diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-13 15:03:12 +0200 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2024-05-13 18:15:46 +0200 |
commit | c46869699aa3ae477516fba0043c2cfd8cda822a (patch) | |
tree | 2a63c9302f530a4da5ee3121eb2a545c39e0fe20 /src | |
parent | 13f7f505375f7c1afd3a7e479a64cc147501b01d (diff) | |
download | lanes-c46869699aa3ae477516fba0043c2cfd8cda822a.tar.gz lanes-c46869699aa3ae477516fba0043c2cfd8cda822a.tar.bz2 lanes-c46869699aa3ae477516fba0043c2cfd8cda822a.zip |
Move InterCopyContext implementation in a separate file
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 18 | ||||
-rw-r--r-- | src/cancel.cpp | 2 | ||||
-rw-r--r-- | src/compat.cpp | 7 | ||||
-rw-r--r-- | src/deep.cpp | 56 | ||||
-rw-r--r-- | src/deep.h | 10 | ||||
-rw-r--r-- | src/intercopycontext.cpp | 1296 | ||||
-rw-r--r-- | src/intercopycontext.h | 77 | ||||
-rw-r--r-- | src/keeper.cpp | 5 | ||||
-rw-r--r-- | src/keeper.h | 1 | ||||
-rw-r--r-- | src/lanes.cpp | 81 | ||||
-rw-r--r-- | src/linda.cpp | 4 | ||||
-rw-r--r-- | src/lindafactory.cpp | 1 | ||||
-rw-r--r-- | src/macros_and_utils.h | 2 | ||||
-rw-r--r-- | src/state.cpp | 1 | ||||
-rw-r--r-- | src/tools.cpp | 1309 | ||||
-rw-r--r-- | src/tools.h | 78 | ||||
-rw-r--r-- | src/universe.cpp | 3 | ||||
-rw-r--r-- | src/universe.h | 1 |
18 files changed, 1488 insertions, 1464 deletions
diff --git a/src/Makefile b/src/Makefile index 06bbcd0..bbe7b23 100644 --- a/src/Makefile +++ b/src/Makefile | |||
@@ -7,7 +7,7 @@ | |||
7 | 7 | ||
8 | MODULE=lanes | 8 | MODULE=lanes |
9 | 9 | ||
10 | SRC=lanes.c cancel.cpp compat.cpp threading.cpp tools.cpp state.cpp linda.cpp lindafactory.cpp deep.cpp keeper.cpp universe.cpp | 10 | SRC=lanes.c cancel.cpp compat.cpp threading.cpp tools.cpp state.cpp linda.cpp lindafactory.cpp deep.cpp keeper.cpp universe.cpp intercopycontext.cpp |
11 | 11 | ||
12 | OBJ=$(SRC:.c=.o) | 12 | OBJ=$(SRC:.c=.o) |
13 | 13 | ||
@@ -124,17 +124,17 @@ all: $(MODULE)/core.$(_SO) | |||
124 | # Note: Don't put $(LUA_LIBS) ahead of $^; MSYS will not like that (I think) | 124 | # Note: Don't put $(LUA_LIBS) ahead of $^; MSYS will not like that (I think) |
125 | # | 125 | # |
126 | $(MODULE_DIR)/core.$(_SO): $(OBJ) | 126 | $(MODULE_DIR)/core.$(_SO): $(OBJ) |
127 | mkdir -p $(MODULE_DIR) | 127 | mkdir -p $(MODULE_DIR) |
128 | $(CC) $(LIBFLAG) $^ $(LIBS) $(LUA_LIBS) -o $@ | 128 | $(CC) $(LIBFLAG) $^ $(LIBS) $(LUA_LIBS) -o $@ |
129 | 129 | ||
130 | clean: | 130 | clean: |
131 | -rm -rf $(MODULE)/core.$(_SO) *.o *.map | 131 | -rm -rf $(MODULE)/core.$(_SO) *.o *.map |
132 | 132 | ||
133 | #--- | 133 | #--- |
134 | # NSLU2 "slug" Linux ARM | 134 | # NSLU2 "slug" Linux ARM |
135 | # | 135 | # |
136 | nslu2: | 136 | nslu2: |
137 | $(MAKE) all CFLAGS="$(CFLAGS) -I/opt/include -L/opt/lib -D_GNU_SOURCE -lpthread" | 137 | $(MAKE) all CFLAGS="$(CFLAGS) -I/opt/include -L/opt/lib -D_GNU_SOURCE -lpthread" |
138 | 138 | ||
139 | #--- | 139 | #--- |
140 | # Cross compiling to Win32 (MinGW on OS X Intel) | 140 | # Cross compiling to Win32 (MinGW on OS X Intel) |
@@ -150,15 +150,15 @@ MINGW_GCC=mingw32-gcc | |||
150 | # i686-pc-mingw32-gcc | 150 | # i686-pc-mingw32-gcc |
151 | 151 | ||
152 | win32: $(WIN32_LUA51)/include/lua.h | 152 | win32: $(WIN32_LUA51)/include/lua.h |
153 | $(MAKE) build CC=$(MINGW_GCC) \ | 153 | $(MAKE) build CC=$(MINGW_GCC) \ |
154 | LUA_FLAGS=-I$(WIN32_LUA51)/include \ | 154 | LUA_FLAGS=-I$(WIN32_LUA51)/include \ |
155 | LUA_LIBS="-L$(WIN32_LUA51) -llua51" \ | 155 | LUA_LIBS="-L$(WIN32_LUA51) -llua51" \ |
156 | _SO=dll \ | 156 | _SO=dll \ |
157 | SO_FLAGS=-shared | 157 | SO_FLAGS=-shared |
158 | 158 | ||
159 | $(WIN32_LUA51)/include/lua.h: | 159 | $(WIN32_LUA51)/include/lua.h: |
160 | @echo "Usage: make win32 WIN32_LUA51=<path of extracted LuaBinaries dll8 and dev packages>" | 160 | @echo "Usage: make win32 WIN32_LUA51=<path of extracted LuaBinaries dll8 and dev packages>" |
161 | @echo " [MINGW_GCC=...mingw32-gcc]" | 161 | @echo " [MINGW_GCC=...mingw32-gcc]" |
162 | @false | 162 | @false |
163 | 163 | ||
164 | .PROXY: all clean nslu2 win32 | 164 | .PROXY: all clean nslu2 win32 |
diff --git a/src/cancel.cpp b/src/cancel.cpp index fe1623b..8356169 100644 --- a/src/cancel.cpp +++ b/src/cancel.cpp | |||
@@ -36,8 +36,6 @@ THE SOFTWARE. | |||
36 | #include "cancel.h" | 36 | #include "cancel.h" |
37 | 37 | ||
38 | #include "lanes_private.h" | 38 | #include "lanes_private.h" |
39 | #include "threading.h" | ||
40 | #include "tools.h" | ||
41 | 39 | ||
42 | // ################################################################################################# | 40 | // ################################################################################################# |
43 | // ################################################################################################# | 41 | // ################################################################################################# |
diff --git a/src/compat.cpp b/src/compat.cpp index 336f716..4e8025e 100644 --- a/src/compat.cpp +++ b/src/compat.cpp | |||
@@ -1,9 +1,12 @@ | |||
1 | #include "compat.h" | ||
2 | |||
3 | #include "macros_and_utils.h" | ||
4 | |||
5 | |||
1 | // ################################################################################################# | 6 | // ################################################################################################# |
2 | // ###################################### Lua 5.1 / 5.2 / 5.3 ###################################### | 7 | // ###################################### Lua 5.1 / 5.2 / 5.3 ###################################### |
3 | // ################################################################################################# | 8 | // ################################################################################################# |
4 | 9 | ||
5 | #include "compat.h" | ||
6 | #include "macros_and_utils.h" | ||
7 | 10 | ||
8 | // ################################################################################################# | 11 | // ################################################################################################# |
9 | 12 | ||
diff --git a/src/deep.cpp b/src/deep.cpp index 6570e55..e0c2a39 100644 --- a/src/deep.cpp +++ b/src/deep.cpp | |||
@@ -34,10 +34,7 @@ THE SOFTWARE. | |||
34 | 34 | ||
35 | #include "deep.h" | 35 | #include "deep.h" |
36 | 36 | ||
37 | #include "compat.h" | ||
38 | #include "tools.h" | 37 | #include "tools.h" |
39 | #include "uniquekey.h" | ||
40 | #include "universe.h" | ||
41 | 38 | ||
42 | #include <bit> | 39 | #include <bit> |
43 | #include <cassert> | 40 | #include <cassert> |
@@ -105,7 +102,7 @@ static void LookupDeep(lua_State* L_) | |||
105 | // ################################################################################################# | 102 | // ################################################################################################# |
106 | 103 | ||
107 | // Return the registered factory for 'index' (deep userdata proxy), or nullptr if 'index' is not a deep userdata proxy. | 104 | // Return the registered factory for 'index' (deep userdata proxy), or nullptr if 'index' is not a deep userdata proxy. |
108 | [[nodiscard]] static inline DeepFactory* LookupFactory(lua_State* L_, int index_, LookupMode mode_) | 105 | [[nodiscard]] DeepFactory* LookupFactory(lua_State* L_, int index_, LookupMode mode_) |
109 | { | 106 | { |
110 | // when looking inside a keeper, we are 100% sure the object is a deep userdata | 107 | // when looking inside a keeper, we are 100% sure the object is a deep userdata |
111 | if (mode_ == LookupMode::FromKeeper) { | 108 | if (mode_ == LookupMode::FromKeeper) { |
@@ -386,54 +383,3 @@ DeepPrelude* DeepFactory::toDeep(lua_State* L_, int index_) const | |||
386 | DeepPrelude** const proxy{ lua_tofulluserdata<DeepPrelude*>(L_, index_) }; | 383 | DeepPrelude** const proxy{ lua_tofulluserdata<DeepPrelude*>(L_, index_) }; |
387 | return *proxy; | 384 | return *proxy; |
388 | } | 385 | } |
389 | |||
390 | // ################################################################################################# | ||
391 | |||
392 | // Copy deep userdata between two separate Lua states (from L1 to L2) | ||
393 | // Returns false if not a deep userdata, else true (unless an error occured) | ||
394 | [[nodiscard]] bool InterCopyContext::tryCopyDeep() const | ||
395 | { | ||
396 | DeepFactory* const factory{ LookupFactory(L1, L1_i, mode) }; | ||
397 | if (factory == nullptr) { | ||
398 | return false; // not a deep userdata | ||
399 | } | ||
400 | |||
401 | STACK_CHECK_START_REL(L1, 0); | ||
402 | STACK_CHECK_START_REL(L2, 0); | ||
403 | |||
404 | // extract all uservalues of the source. unfortunately, the only way to know their count is to iterate until we fail | ||
405 | int nuv = 0; | ||
406 | while (lua_getiuservalue(L1, L1_i, nuv + 1) != LUA_TNONE) { // L1: ... u [uv]* nil | ||
407 | ++nuv; | ||
408 | } | ||
409 | // last call returned TNONE and pushed nil, that we don't need | ||
410 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
411 | STACK_CHECK(L1, nuv); | ||
412 | |||
413 | DeepPrelude* const u{ *lua_tofulluserdata<DeepPrelude*>(L1, L1_i) }; | ||
414 | char const* errmsg{ DeepFactory::PushDeepProxy(L2, u, nuv, mode) }; // L1: ... u [uv]* L2: u | ||
415 | if (errmsg != nullptr) { | ||
416 | raise_luaL_error(getErrL(), errmsg); | ||
417 | } | ||
418 | |||
419 | // transfer all uservalues of the source in the destination | ||
420 | { | ||
421 | InterCopyContext c{ U, L2, L1, L2_cache_i, {}, VT::NORMAL, mode, name }; | ||
422 | int const clone_i{ lua_gettop(L2) }; | ||
423 | while (nuv) { | ||
424 | c.L1_i = SourceIndex{ lua_absindex(L1, -1) }; | ||
425 | if (!c.inter_copy_one()) { // L1: ... u [uv]* L2: u uv | ||
426 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
427 | } | ||
428 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
429 | // this pops the value from the stack | ||
430 | lua_setiuservalue(L2, clone_i, nuv); // L2: u | ||
431 | --nuv; | ||
432 | } | ||
433 | } | ||
434 | |||
435 | STACK_CHECK(L2, 1); | ||
436 | STACK_CHECK(L1, 0); | ||
437 | |||
438 | return true; | ||
439 | } \ No newline at end of file | ||
@@ -20,15 +20,9 @@ extern "C" | |||
20 | #include <atomic> | 20 | #include <atomic> |
21 | 21 | ||
22 | // forwards | 22 | // forwards |
23 | enum class LookupMode; | ||
23 | class Universe; | 24 | class Universe; |
24 | 25 | ||
25 | enum class LookupMode | ||
26 | { | ||
27 | LaneBody, // send the lane body directly from the source to the destination lane. keep this one first so that it's the value we get when we default-construct | ||
28 | ToKeeper, // send a function from a lane to a keeper state | ||
29 | FromKeeper // send a function from a keeper state to a lane | ||
30 | }; | ||
31 | |||
32 | // ################################################################################################# | 26 | // ################################################################################################# |
33 | 27 | ||
34 | // xxh64 of string "kDeepVersion_1" generated at https://www.pelock.com/products/hash-calculator | 28 | // xxh64 of string "kDeepVersion_1" generated at https://www.pelock.com/products/hash-calculator |
@@ -80,3 +74,5 @@ class DeepFactory | |||
80 | static void DeleteDeepObject(lua_State* L_, DeepPrelude* o_); | 74 | static void DeleteDeepObject(lua_State* L_, DeepPrelude* o_); |
81 | [[nodiscard]] static char const* PushDeepProxy(DestState L_, DeepPrelude* o_, int nuv_, LookupMode mode_); | 75 | [[nodiscard]] static char const* PushDeepProxy(DestState L_, DeepPrelude* o_, int nuv_, LookupMode mode_); |
82 | }; | 76 | }; |
77 | |||
78 | [[nodiscard]] DeepFactory* LookupFactory(lua_State* L_, int index_, LookupMode mode_); | ||
diff --git a/src/intercopycontext.cpp b/src/intercopycontext.cpp new file mode 100644 index 0000000..07fcd77 --- /dev/null +++ b/src/intercopycontext.cpp | |||
@@ -0,0 +1,1296 @@ | |||
1 | /* | ||
2 | =============================================================================== | ||
3 | |||
4 | Copyright (C) 2024 benoit Germain <bnt.germain@gmail.com> | ||
5 | |||
6 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
7 | of this software and associated documentation files (the "Software"), to deal | ||
8 | in the Software without restriction, including without limitation the rights | ||
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
10 | copies of the Software, and to permit persons to whom the Software is | ||
11 | furnished to do so, subject to the following conditions: | ||
12 | |||
13 | The above copyright notice and this permission notice shall be included in | ||
14 | all copies or substantial portions of the Software. | ||
15 | |||
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
22 | THE SOFTWARE. | ||
23 | |||
24 | =============================================================================== | ||
25 | */ | ||
26 | |||
27 | #include "intercopycontext.h" | ||
28 | |||
29 | #include "deep.h" | ||
30 | #include "universe.h" | ||
31 | |||
32 | // ################################################################################################# | ||
33 | |||
34 | // Lua 5.4.3 style of dumping (see lstrlib.c) | ||
35 | // we have to do it that way because we can't unbalance the stack between buffer operations | ||
36 | // namely, this means we can't push a function on top of the stack *after* we initialize the buffer! | ||
37 | // luckily, this also works with earlier Lua versions | ||
38 | [[nodiscard]] static int buf_writer(lua_State* L_, void const* b_, size_t size_, void* ud_) | ||
39 | { | ||
40 | luaL_Buffer* const B{ static_cast<luaL_Buffer*>(ud_) }; | ||
41 | if (!B->L) { | ||
42 | luaL_buffinit(L_, B); | ||
43 | } | ||
44 | luaL_addlstring(B, static_cast<char const*>(b_), size_); | ||
45 | return 0; | ||
46 | } | ||
47 | |||
48 | // ################################################################################################# | ||
49 | |||
50 | // function sentinel used to transfer native functions from/to keeper states | ||
51 | [[nodiscard]] static int func_lookup_sentinel(lua_State* L_) | ||
52 | { | ||
53 | raise_luaL_error(L_, "function lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | ||
54 | } | ||
55 | |||
56 | // ################################################################################################# | ||
57 | |||
58 | // function sentinel used to transfer native table from/to keeper states | ||
59 | [[nodiscard]] static int table_lookup_sentinel(lua_State* L_) | ||
60 | { | ||
61 | raise_luaL_error(L_, "table lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | ||
62 | } | ||
63 | |||
64 | // ################################################################################################# | ||
65 | |||
66 | // function sentinel used to transfer cloned full userdata from/to keeper states | ||
67 | [[nodiscard]] static int userdata_clone_sentinel(lua_State* L_) | ||
68 | { | ||
69 | raise_luaL_error(L_, "userdata clone sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | ||
70 | } | ||
71 | |||
72 | // ################################################################################################# | ||
73 | |||
74 | // retrieve the name of a function/table in the lookup database | ||
75 | [[nodiscard]] static char const* find_lookup_name(lua_State* L_, int i_, LookupMode mode_, char const* upName_, size_t* len_) | ||
76 | { | ||
77 | LUA_ASSERT(L_, lua_isfunction(L_, i_) || lua_istable(L_, i_)); // L_: ... v ... | ||
78 | STACK_CHECK_START_REL(L_, 0); | ||
79 | STACK_GROW(L_, 3); // up to 3 slots are necessary on error | ||
80 | if (mode_ == LookupMode::FromKeeper) { | ||
81 | lua_CFunction f = lua_tocfunction(L_, i_); // should *always* be one of the function sentinels | ||
82 | if (f == func_lookup_sentinel || f == table_lookup_sentinel || f == userdata_clone_sentinel) { | ||
83 | lua_getupvalue(L_, i_, 1); // L_: ... v ... "f.q.n" | ||
84 | } else { | ||
85 | // if this is not a sentinel, this is some user-created table we wanted to lookup | ||
86 | LUA_ASSERT(L_, nullptr == f && lua_istable(L_, i_)); | ||
87 | // push anything that will convert to nullptr string | ||
88 | lua_pushnil(L_); // L_: ... v ... nil | ||
89 | } | ||
90 | } else { | ||
91 | // fetch the name from the source state's lookup table | ||
92 | kLookupRegKey.pushValue(L_); // L_: ... v ... {} | ||
93 | STACK_CHECK(L_, 1); | ||
94 | LUA_ASSERT(L_, lua_istable(L_, -1)); | ||
95 | lua_pushvalue(L_, i_); // L_: ... v ... {} v | ||
96 | lua_rawget(L_, -2); // L_: ... v ... {} "f.q.n" | ||
97 | } | ||
98 | char const* fqn{ lua_tolstring(L_, -1, len_) }; | ||
99 | DEBUGSPEW_CODE(Universe* const U = universe_get(L_)); | ||
100 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "function [C] %s \n" INDENT_END(U), fqn)); | ||
101 | // popping doesn't invalidate the pointer since this is an interned string gotten from the lookup database | ||
102 | lua_pop(L_, (mode_ == LookupMode::FromKeeper) ? 1 : 2); // L_: ... v ... | ||
103 | STACK_CHECK(L_, 0); | ||
104 | if (nullptr == fqn && !lua_istable(L_, i_)) { // raise an error if we try to send an unknown function (but not for tables) | ||
105 | *len_ = 0; // just in case | ||
106 | // try to discover the name of the function we want to send | ||
107 | lua_getglobal(L_, "decoda_name"); // L_: ... v ... decoda_name | ||
108 | char const* from{ lua_tostring(L_, -1) }; | ||
109 | lua_pushcfunction(L_, luaG_nameof); // L_: ... v ... decoda_name luaG_nameof | ||
110 | lua_pushvalue(L_, i_); // L_: ... v ... decoda_name luaG_nameof t | ||
111 | lua_call(L_, 1, 2); // L_: ... v ... decoda_name "type" "name"|nil | ||
112 | char const* typewhat{ (lua_type(L_, -2) == LUA_TSTRING) ? lua_tostring(L_, -2) : luaL_typename(L_, -2) }; | ||
113 | // second return value can be nil if the table was not found | ||
114 | // probable reason: the function was removed from the source Lua state before Lanes was required. | ||
115 | char const *what, *gotchaA, *gotchaB; | ||
116 | if (lua_isnil(L_, -1)) { | ||
117 | gotchaA = " referenced by"; | ||
118 | gotchaB = "\n(did you remove it from the source Lua state before requiring Lanes?)"; | ||
119 | what = upName_; | ||
120 | } else { | ||
121 | gotchaA = ""; | ||
122 | gotchaB = ""; | ||
123 | what = (lua_type(L_, -1) == LUA_TSTRING) ? lua_tostring(L_, -1) : luaL_typename(L_, -1); | ||
124 | } | ||
125 | raise_luaL_error(L_, "%s%s '%s' not found in %s origin transfer database.%s", typewhat, gotchaA, what, from ? from : "main", gotchaB); | ||
126 | } | ||
127 | STACK_CHECK(L_, 0); | ||
128 | return fqn; | ||
129 | } | ||
130 | |||
131 | // ################################################################################################# | ||
132 | |||
133 | /*---=== Inter-state copying ===---*/ | ||
134 | |||
135 | // xxh64 of string "kMtIdRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
136 | static constexpr RegistryUniqueKey kMtIdRegKey{ 0xA8895DCF4EC3FE3Cull }; | ||
137 | |||
138 | // get a unique ID for metatable at [i]. | ||
139 | [[nodiscard]] static lua_Integer get_mt_id(Universe* U_, lua_State* L_, int idx_) | ||
140 | { | ||
141 | idx_ = lua_absindex(L_, idx_); | ||
142 | |||
143 | STACK_GROW(L_, 3); | ||
144 | |||
145 | STACK_CHECK_START_REL(L_, 0); | ||
146 | std::ignore = kMtIdRegKey.getSubTable(L_, 0, 0); // L_: ... _R[kMtIdRegKey] | ||
147 | lua_pushvalue(L_, idx_); // L_: ... _R[kMtIdRegKey] {mt} | ||
148 | lua_rawget(L_, -2); // L_: ... _R[kMtIdRegKey] mtk? | ||
149 | |||
150 | lua_Integer id{ lua_tointeger(L_, -1) }; // 0 for nil | ||
151 | lua_pop(L_, 1); // L_: ... _R[kMtIdRegKey] | ||
152 | STACK_CHECK(L_, 1); | ||
153 | |||
154 | if (id == 0) { | ||
155 | id = U_->nextMetatableId.fetch_add(1, std::memory_order_relaxed); | ||
156 | |||
157 | // Create two-way references: id_uint <-> table | ||
158 | lua_pushvalue(L_, idx_); // L_: ... _R[kMtIdRegKey] {mt} | ||
159 | lua_pushinteger(L_, id); // L_: ... _R[kMtIdRegKey] {mt} id | ||
160 | lua_rawset(L_, -3); // L_: ... _R[kMtIdRegKey] | ||
161 | |||
162 | lua_pushinteger(L_, id); // L_: ... _R[kMtIdRegKey] id | ||
163 | lua_pushvalue(L_, idx_); // L_: ... _R[kMtIdRegKey] id {mt} | ||
164 | lua_rawset(L_, -3); // L_: ... _R[kMtIdRegKey] | ||
165 | } | ||
166 | lua_pop(L_, 1); // L_: ... | ||
167 | STACK_CHECK(L_, 0); | ||
168 | |||
169 | return id; | ||
170 | } | ||
171 | |||
172 | // ################################################################################################# | ||
173 | |||
174 | // Copy a function over, which has not been found in the cache. | ||
175 | // L2 has the cache key for this function at the top of the stack | ||
176 | void InterCopyContext::copy_func() const | ||
177 | { | ||
178 | LUA_ASSERT(L1, L2_cache_i != 0); // L2: ... {cache} ... p | ||
179 | STACK_GROW(L1, 2); | ||
180 | STACK_CHECK_START_REL(L1, 0); | ||
181 | |||
182 | // 'lua_dump()' needs the function at top of stack | ||
183 | // if already on top of the stack, no need to push again | ||
184 | bool const needToPush{ L1_i != lua_gettop(L1) }; | ||
185 | if (needToPush) { | ||
186 | lua_pushvalue(L1, L1_i); // L1: ... f | ||
187 | } | ||
188 | |||
189 | // | ||
190 | // "value returned is the error code returned by the last call | ||
191 | // to the writer" (and we only return 0) | ||
192 | // not sure this could ever fail but for memory shortage reasons | ||
193 | // last parameter is Lua 5.4-specific (no stripping) | ||
194 | luaL_Buffer B; | ||
195 | B.L = nullptr; | ||
196 | if (lua504_dump(L1, buf_writer, &B, 0) != 0) { | ||
197 | raise_luaL_error(getErrL(), "internal error: function dump failed."); | ||
198 | } | ||
199 | |||
200 | // pushes dumped string on 'L1' | ||
201 | luaL_pushresult(&B); // L1: ... f b | ||
202 | |||
203 | // if not pushed, no need to pop | ||
204 | if (needToPush) { | ||
205 | lua_remove(L1, -2); // L1: ... b | ||
206 | } | ||
207 | |||
208 | // transfer the bytecode, then the upvalues, to create a similar closure | ||
209 | { | ||
210 | char const* fname = nullptr; | ||
211 | #define LOG_FUNC_INFO 0 | ||
212 | #if LOG_FUNC_INFO | ||
213 | // "To get information about a function you push it onto the | ||
214 | // stack and start the what string with the character '>'." | ||
215 | // | ||
216 | { | ||
217 | lua_Debug ar; | ||
218 | lua_pushvalue(L1, L1_i); // L1: ... b f | ||
219 | // fills 'fname' 'namewhat' and 'linedefined', pops function | ||
220 | lua_getinfo(L1, ">nS", &ar); // L1: ... b | ||
221 | fname = ar.namewhat; | ||
222 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "FNAME: %s @ %d" INDENT_END(U), ar.short_src, ar.linedefined)); // just gives nullptr | ||
223 | } | ||
224 | #endif // LOG_FUNC_INFO | ||
225 | { | ||
226 | size_t sz; | ||
227 | char const* s = lua_tolstring(L1, -1, &sz); // L1: ... b | ||
228 | LUA_ASSERT(L1, s && sz); | ||
229 | STACK_GROW(L2, 2); | ||
230 | // Note: Line numbers seem to be taken precisely from the | ||
231 | // original function. 'fname' is not used since the chunk | ||
232 | // is precompiled (it seems...). | ||
233 | // | ||
234 | // TBD: Can we get the function's original name through, as well? | ||
235 | // | ||
236 | if (luaL_loadbuffer(L2, s, sz, fname) != 0) { // L2: ... {cache} ... p function | ||
237 | // chunk is precompiled so only LUA_ERRMEM can happen | ||
238 | // "Otherwise, it pushes an error message" | ||
239 | // | ||
240 | STACK_GROW(L1, 1); | ||
241 | raise_luaL_error(getErrL(), "%s: %s", fname, lua_tostring(L2, -1)); | ||
242 | } | ||
243 | // remove the dumped string | ||
244 | lua_pop(L1, 1); // ... | ||
245 | // now set the cache as soon as we can. | ||
246 | // this is necessary if one of the function's upvalues references it indirectly | ||
247 | // we need to find it in the cache even if it isn't fully transfered yet | ||
248 | lua_insert(L2, -2); // L2: ... {cache} ... function p | ||
249 | lua_pushvalue(L2, -2); // L2: ... {cache} ... function p function | ||
250 | // cache[p] = function | ||
251 | lua_rawset(L2, L2_cache_i); // L2: ... {cache} ... function | ||
252 | } | ||
253 | STACK_CHECK(L1, 0); | ||
254 | |||
255 | /* push over any upvalues; references to this function will come from | ||
256 | * cache so we don't end up in eternal loop. | ||
257 | * Lua5.2 and Lua5.3: one of the upvalues is _ENV, which we don't want to copy! | ||
258 | * instead, the function shall have LUA_RIDX_GLOBALS taken in the destination state! | ||
259 | */ | ||
260 | int n{ 0 }; | ||
261 | { | ||
262 | InterCopyContext c{ U, L2, L1, L2_cache_i, {}, VT::NORMAL, mode, {} }; | ||
263 | #if LUA_VERSION_NUM >= 502 | ||
264 | // Starting with Lua 5.2, each Lua function gets its environment as one of its upvalues (named LUA_ENV, aka "_ENV" by default) | ||
265 | // Generally this is LUA_RIDX_GLOBALS, which we don't want to copy from the source to the destination state... | ||
266 | // -> if we encounter an upvalue equal to the global table in the source, bind it to the destination's global table | ||
267 | lua_pushglobaltable(L1); // L1: ... _G | ||
268 | #endif // LUA_VERSION_NUM | ||
269 | for (n = 0; (c.name = lua_getupvalue(L1, L1_i, 1 + n)) != nullptr; ++n) { // L1: ... _G up[n] | ||
270 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "UPNAME[%d]: %s -> " INDENT_END(U), n, c.name)); | ||
271 | #if LUA_VERSION_NUM >= 502 | ||
272 | if (lua_rawequal(L1, -1, -2)) { // is the upvalue equal to the global table? | ||
273 | DEBUGSPEW_CODE(fprintf(stderr, "pushing destination global scope\n")); | ||
274 | lua_pushglobaltable(L2); // L2: ... {cache} ... function <upvalues> | ||
275 | } else | ||
276 | #endif // LUA_VERSION_NUM | ||
277 | { | ||
278 | DEBUGSPEW_CODE(fprintf(stderr, "copying value\n")); | ||
279 | c.L1_i = SourceIndex{ lua_gettop(L1) }; | ||
280 | if (!c.inter_copy_one()) { // L2: ... {cache} ... function <upvalues> | ||
281 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
282 | } | ||
283 | } | ||
284 | lua_pop(L1, 1); // L1: ... _G | ||
285 | } | ||
286 | #if LUA_VERSION_NUM >= 502 | ||
287 | lua_pop(L1, 1); // L1: ... | ||
288 | #endif // LUA_VERSION_NUM | ||
289 | } | ||
290 | // L2: ... {cache} ... function + 'n' upvalues (>=0) | ||
291 | |||
292 | STACK_CHECK(L1, 0); | ||
293 | |||
294 | // Set upvalues (originally set to 'nil' by 'lua_load') | ||
295 | for (int const func_index{ lua_gettop(L2) - n }; n > 0; --n) { | ||
296 | char const* rc{ lua_setupvalue(L2, func_index, n) }; // L2: ... {cache} ... function | ||
297 | // | ||
298 | // "assigns the value at the top of the stack to the upvalue and returns its name. | ||
299 | // It also pops the value from the stack." | ||
300 | |||
301 | LUA_ASSERT(L1, rc); // not having enough slots? | ||
302 | } | ||
303 | // once all upvalues have been set we are left | ||
304 | // with the function at the top of the stack // L2: ... {cache} ... function | ||
305 | } | ||
306 | STACK_CHECK(L1, 0); | ||
307 | } | ||
308 | |||
309 | // ################################################################################################# | ||
310 | |||
311 | // Push a looked-up native/LuaJIT function. | ||
312 | void InterCopyContext::lookup_native_func() const | ||
313 | { | ||
314 | // get the name of the function we want to send | ||
315 | size_t len; | ||
316 | char const* const fqn{ find_lookup_name(L1, L1_i, mode, name, &len) }; | ||
317 | // push the equivalent function in the destination's stack, retrieved from the lookup table | ||
318 | STACK_CHECK_START_REL(L2, 0); | ||
319 | STACK_GROW(L2, 3); // up to 3 slots are necessary on error | ||
320 | switch (mode) { | ||
321 | default: // shouldn't happen, in theory... | ||
322 | raise_luaL_error(getErrL(), "internal error: unknown lookup mode"); | ||
323 | break; | ||
324 | |||
325 | case LookupMode::ToKeeper: | ||
326 | // push a sentinel closure that holds the lookup name as upvalue | ||
327 | lua_pushlstring(L2, fqn, len); // L1: ... f ... L2: "f.q.n" | ||
328 | lua_pushcclosure(L2, func_lookup_sentinel, 1); // L1: ... f ... L2: f | ||
329 | break; | ||
330 | |||
331 | case LookupMode::LaneBody: | ||
332 | case LookupMode::FromKeeper: | ||
333 | kLookupRegKey.pushValue(L2); // L1: ... f ... L2: {} | ||
334 | STACK_CHECK(L2, 1); | ||
335 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
336 | lua_pushlstring(L2, fqn, len); // L1: ... f ... L2: {} "f.q.n" | ||
337 | lua_rawget(L2, -2); // L1: ... f ... L2: {} f | ||
338 | // nil means we don't know how to transfer stuff: user should do something | ||
339 | // anything other than function or table should not happen! | ||
340 | if (!lua_isfunction(L2, -1) && !lua_istable(L2, -1)) { | ||
341 | lua_getglobal(L1, "decoda_name"); // L1: ... f ... decoda_name | ||
342 | char const* const from{ lua_tostring(L1, -1) }; | ||
343 | lua_pop(L1, 1); // L1: ... f ... | ||
344 | lua_getglobal(L2, "decoda_name"); // L1: ... f ... L2: {} f decoda_name | ||
345 | char const* const to{ lua_tostring(L2, -1) }; | ||
346 | lua_pop(L2, 1); // L2: {} f | ||
347 | // when mode_ == LookupMode::FromKeeper, L is a keeper state and L2 is not, therefore L2 is the state where we want to raise the error | ||
348 | raise_luaL_error( | ||
349 | getErrL(), | ||
350 | "%s%s: function '%s' not found in %s destination transfer database.", | ||
351 | lua_isnil(L2, -1) ? "" : "INTERNAL ERROR IN ", | ||
352 | from ? from : "main", | ||
353 | fqn, | ||
354 | to ? to : "main"); | ||
355 | return; | ||
356 | } | ||
357 | lua_remove(L2, -2); // L2: f | ||
358 | break; | ||
359 | |||
360 | /* keep it in case I need it someday, who knows... | ||
361 | case LookupMode::RawFunctions: | ||
362 | { | ||
363 | int n; | ||
364 | char const* upname; | ||
365 | lua_CFunction f = lua_tocfunction( L, i); | ||
366 | // copy upvalues | ||
367 | for (n = 0; (upname = lua_getupvalue( L, i, 1 + n)) != nullptr; ++ n) { | ||
368 | luaG_inter_move( U, L, L2, 1, mode_); // L2: [up[,up ...]] | ||
369 | } | ||
370 | lua_pushcclosure( L2, f, n); // L2: | ||
371 | } | ||
372 | break; | ||
373 | */ | ||
374 | } | ||
375 | STACK_CHECK(L2, 1); | ||
376 | } | ||
377 | |||
378 | // ################################################################################################# | ||
379 | |||
380 | // Check if we've already copied the same function from 'L1', and reuse the old copy. | ||
381 | // Always pushes a function to 'L2'. | ||
382 | void InterCopyContext::copy_cached_func() const | ||
383 | { | ||
384 | FuncSubType const funcSubType{ luaG_getfuncsubtype(L1, L1_i) }; | ||
385 | if (funcSubType == FuncSubType::Bytecode) { | ||
386 | void* const aspointer = const_cast<void*>(lua_topointer(L1, L1_i)); | ||
387 | // TBD: Merge this and same code for tables | ||
388 | LUA_ASSERT(L1, L2_cache_i != 0); | ||
389 | |||
390 | STACK_GROW(L2, 2); | ||
391 | |||
392 | // L2_cache[id_str]= function | ||
393 | // | ||
394 | STACK_CHECK_START_REL(L2, 0); | ||
395 | |||
396 | // We don't need to use the from state ('L1') in ID since the life span | ||
397 | // is only for the duration of a copy (both states are locked). | ||
398 | |||
399 | // push a light userdata uniquely representing the function | ||
400 | lua_pushlightuserdata(L2, aspointer); // L2: ... {cache} ... p | ||
401 | |||
402 | // fprintf( stderr, "<< ID: %s >>\n", lua_tostring( L2, -1)); | ||
403 | |||
404 | lua_pushvalue(L2, -1); // L2: ... {cache} ... p p | ||
405 | lua_rawget(L2, L2_cache_i); // L2: ... {cache} ... p function|nil|true | ||
406 | |||
407 | if (lua_isnil(L2, -1)) { // function is unknown | ||
408 | lua_pop(L2, 1); // L2: ... {cache} ... p | ||
409 | |||
410 | // Set to 'true' for the duration of creation; need to find self-references | ||
411 | // via upvalues | ||
412 | // | ||
413 | // pushes a copy of the func, stores a reference in the cache | ||
414 | copy_func(); // L2: ... {cache} ... function | ||
415 | } else { // found function in the cache | ||
416 | lua_remove(L2, -2); // L2: ... {cache} ... function | ||
417 | } | ||
418 | STACK_CHECK(L2, 1); | ||
419 | LUA_ASSERT(L1, lua_isfunction(L2, -1)); | ||
420 | } else { // function is native/LuaJIT: no need to cache | ||
421 | lookup_native_func(); // L2: ... {cache} ... function | ||
422 | // if the function was in fact a lookup sentinel, we can either get a function or a table here | ||
423 | LUA_ASSERT(L1, lua_isfunction(L2, -1) || lua_istable(L2, -1)); | ||
424 | } | ||
425 | } | ||
426 | |||
427 | // ################################################################################################# | ||
428 | |||
429 | // Push a looked-up table, or nothing if we found nothing | ||
430 | [[nodiscard]] bool InterCopyContext::lookup_table() const | ||
431 | { | ||
432 | // get the name of the table we want to send | ||
433 | size_t len; | ||
434 | char const* fqn = find_lookup_name(L1, L1_i, mode, name, &len); | ||
435 | if (nullptr == fqn) { // name not found, it is some user-created table | ||
436 | return false; | ||
437 | } | ||
438 | // push the equivalent table in the destination's stack, retrieved from the lookup table | ||
439 | STACK_CHECK_START_REL(L2, 0); | ||
440 | STACK_GROW(L2, 3); // up to 3 slots are necessary on error | ||
441 | switch (mode) { | ||
442 | default: // shouldn't happen, in theory... | ||
443 | raise_luaL_error(getErrL(), "internal error: unknown lookup mode"); | ||
444 | break; | ||
445 | |||
446 | case LookupMode::ToKeeper: | ||
447 | // push a sentinel closure that holds the lookup name as upvalue | ||
448 | lua_pushlstring(L2, fqn, len); // L1: ... t ... L2: "f.q.n" | ||
449 | lua_pushcclosure(L2, table_lookup_sentinel, 1); // L1: ... t ... L2: f | ||
450 | break; | ||
451 | |||
452 | case LookupMode::LaneBody: | ||
453 | case LookupMode::FromKeeper: | ||
454 | kLookupRegKey.pushValue(L2); // L1: ... t ... L2: {} | ||
455 | STACK_CHECK(L2, 1); | ||
456 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
457 | lua_pushlstring(L2, fqn, len); // L2: {} "f.q.n" | ||
458 | lua_rawget(L2, -2); // L2: {} t | ||
459 | // we accept destination lookup failures in the case of transfering the Lanes body function (this will result in the source table being cloned instead) | ||
460 | // but not when we extract something out of a keeper, as there is nothing to clone! | ||
461 | if (lua_isnil(L2, -1) && mode == LookupMode::LaneBody) { | ||
462 | lua_pop(L2, 2); // L1: ... t ... L2: | ||
463 | STACK_CHECK(L2, 0); | ||
464 | return false; | ||
465 | } else if (!lua_istable(L2, -1)) { // this can happen if someone decides to replace same already registered item (for a example a standard lib function) with a table | ||
466 | lua_getglobal(L1, "decoda_name"); // L1: ... t ... decoda_name | ||
467 | char const* from{ lua_tostring(L1, -1) }; | ||
468 | lua_pop(L1, 1); // L1: ... t ... | ||
469 | lua_getglobal(L2, "decoda_name"); // L1: ... t ... L2: {} t decoda_name | ||
470 | char const* to{ lua_tostring(L2, -1) }; | ||
471 | lua_pop(L2, 1); // L1: ... t ... L2: {} t | ||
472 | raise_luaL_error( | ||
473 | getErrL(), | ||
474 | "%s: source table '%s' found as %s in %s destination transfer database.", | ||
475 | from ? from : "main", | ||
476 | fqn, | ||
477 | lua_typename(L2, lua_type_as_enum(L2, -1)), | ||
478 | to ? to : "main"); | ||
479 | } | ||
480 | lua_remove(L2, -2); // L1: ... t ... L2: t | ||
481 | break; | ||
482 | } | ||
483 | STACK_CHECK(L2, 1); | ||
484 | return true; | ||
485 | } | ||
486 | |||
487 | // ################################################################################################# | ||
488 | |||
489 | void InterCopyContext::inter_copy_keyvaluepair() const | ||
490 | { | ||
491 | SourceIndex const val_i{ lua_gettop(L1) }; | ||
492 | SourceIndex const key_i{ val_i - 1 }; | ||
493 | |||
494 | // For the key, only basic key types are copied over. others ignored | ||
495 | InterCopyContext c{ U, L2, L1, L2_cache_i, key_i, VT::KEY, mode, name }; | ||
496 | if (!c.inter_copy_one()) { | ||
497 | return; | ||
498 | // we could raise an error instead of ignoring the table entry, like so: | ||
499 | // raise_luaL_error(L1, "Unable to copy %s key '%s' because of value is of type '%s'", (vt == VT::NORMAL) ? "table" : "metatable", name, luaL_typename(L1, key_i)); | ||
500 | // maybe offer this possibility as a global configuration option, or a linda setting, or as a parameter of the call causing the transfer? | ||
501 | } | ||
502 | |||
503 | char* valPath{ nullptr }; | ||
504 | if (U->verboseErrors) { | ||
505 | // for debug purposes, let's try to build a useful name | ||
506 | if (lua_type(L1, key_i) == LUA_TSTRING) { | ||
507 | char const* key{ lua_tostring(L1, key_i) }; | ||
508 | size_t const keyRawLen = lua_rawlen(L1, key_i); | ||
509 | size_t const bufLen = strlen(name) + keyRawLen + 2; | ||
510 | valPath = (char*) alloca(bufLen); | ||
511 | sprintf(valPath, "%s.%*s", name, (int) keyRawLen, key); | ||
512 | key = nullptr; | ||
513 | } | ||
514 | #if defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
515 | else if (lua_isinteger(L1, key_i)) { | ||
516 | lua_Integer const key{ lua_tointeger(L1, key_i) }; | ||
517 | valPath = (char*) alloca(strlen(name) + 32 + 3); | ||
518 | sprintf(valPath, "%s[" LUA_INTEGER_FMT "]", name, key); | ||
519 | } | ||
520 | #endif // defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
521 | else if (lua_type(L1, key_i) == LUA_TNUMBER) { | ||
522 | lua_Number const key{ lua_tonumber(L1, key_i) }; | ||
523 | valPath = (char*) alloca(strlen(name) + 32 + 3); | ||
524 | sprintf(valPath, "%s[" LUA_NUMBER_FMT "]", name, key); | ||
525 | } else if (lua_type(L1, key_i) == LUA_TLIGHTUSERDATA) { | ||
526 | void* const key{ lua_touserdata(L1, key_i) }; | ||
527 | valPath = (char*) alloca(strlen(name) + 16 + 5); | ||
528 | sprintf(valPath, "%s[U:%p]", name, key); | ||
529 | } else if (lua_type(L1, key_i) == LUA_TBOOLEAN) { | ||
530 | int const key{ lua_toboolean(L1, key_i) }; | ||
531 | valPath = (char*) alloca(strlen(name) + 8); | ||
532 | sprintf(valPath, "%s[%s]", name, key ? "true" : "false"); | ||
533 | } | ||
534 | } | ||
535 | c.L1_i = SourceIndex{ val_i }; | ||
536 | // Contents of metatables are copied with cache checking. important to detect loops. | ||
537 | c.vt = VT::NORMAL; | ||
538 | c.name = valPath ? valPath : name; | ||
539 | if (c.inter_copy_one()) { | ||
540 | LUA_ASSERT(L1, lua_istable(L2, -3)); | ||
541 | lua_rawset(L2, -3); // add to table (pops key & val) | ||
542 | } else { | ||
543 | raise_luaL_error(getErrL(), "Unable to copy %s entry '%s' because of value is of type '%s'", (vt == VT::NORMAL) ? "table" : "metatable", valPath, luaL_typename(L1, val_i)); | ||
544 | } | ||
545 | } | ||
546 | |||
547 | // ################################################################################################# | ||
548 | |||
549 | [[nodiscard]] bool InterCopyContext::push_cached_metatable() const | ||
550 | { | ||
551 | STACK_CHECK_START_REL(L1, 0); | ||
552 | if (!lua_getmetatable(L1, L1_i)) { // L1: ... mt | ||
553 | STACK_CHECK(L1, 0); | ||
554 | return false; | ||
555 | } | ||
556 | STACK_CHECK(L1, 1); | ||
557 | |||
558 | lua_Integer const mt_id{ get_mt_id(U, L1, -1) }; // Unique id for the metatable | ||
559 | |||
560 | STACK_CHECK_START_REL(L2, 0); | ||
561 | STACK_GROW(L2, 4); | ||
562 | // do we already know this metatable? | ||
563 | std::ignore = kMtIdRegKey.getSubTable(L2, 0, 0); // L2: _R[kMtIdRegKey] | ||
564 | lua_pushinteger(L2, mt_id); // L2: _R[kMtIdRegKey] id | ||
565 | lua_rawget(L2, -2); // L2: _R[kMtIdRegKey] mt|nil | ||
566 | STACK_CHECK(L2, 2); | ||
567 | |||
568 | if (lua_isnil(L2, -1)) { // L2 did not know the metatable | ||
569 | lua_pop(L2, 1); // L2: _R[kMtIdRegKey] | ||
570 | InterCopyContext const c{ U, L2, L1, L2_cache_i, SourceIndex{ lua_gettop(L1) }, VT::METATABLE, mode, name }; | ||
571 | if (!c.inter_copy_one()) { // L2: _R[kMtIdRegKey] mt? | ||
572 | raise_luaL_error(getErrL(), "Error copying a metatable"); | ||
573 | } | ||
574 | |||
575 | STACK_CHECK(L2, 2); // L2: _R[kMtIdRegKey] mt | ||
576 | // mt_id -> metatable | ||
577 | lua_pushinteger(L2, mt_id); // L2: _R[kMtIdRegKey] mt id | ||
578 | lua_pushvalue(L2, -2); // L2: _R[kMtIdRegKey] mt id mt | ||
579 | lua_rawset(L2, -4); // L2: _R[kMtIdRegKey] mt | ||
580 | |||
581 | // metatable -> mt_id | ||
582 | lua_pushvalue(L2, -1); // L2: _R[kMtIdRegKey] mt mt | ||
583 | lua_pushinteger(L2, mt_id); // L2: _R[kMtIdRegKey] mt mt id | ||
584 | lua_rawset(L2, -4); // L2: _R[kMtIdRegKey] mt | ||
585 | STACK_CHECK(L2, 2); | ||
586 | } | ||
587 | lua_remove(L2, -2); // L2: mt | ||
588 | |||
589 | lua_pop(L1, 1); // L1: ... | ||
590 | STACK_CHECK(L2, 1); | ||
591 | STACK_CHECK(L1, 0); | ||
592 | return true; | ||
593 | } | ||
594 | |||
595 | // ################################################################################################# | ||
596 | |||
597 | // Check if we've already copied the same table from 'L1', and reuse the old copy. This allows table upvalues shared by multiple | ||
598 | // local functions to point to the same table, also in the target. | ||
599 | // Always pushes a table to 'L2'. | ||
600 | // Returns true if the table was cached (no need to fill it!); false if it's a virgin. | ||
601 | [[nodiscard]] bool InterCopyContext::push_cached_table() const | ||
602 | { | ||
603 | void const* p{ lua_topointer(L1, L1_i) }; | ||
604 | |||
605 | LUA_ASSERT(L1, L2_cache_i != 0); | ||
606 | STACK_GROW(L2, 3); | ||
607 | STACK_CHECK_START_REL(L2, 0); | ||
608 | |||
609 | // We don't need to use the from state ('L1') in ID since the life span | ||
610 | // is only for the duration of a copy (both states are locked). | ||
611 | // push a light userdata uniquely representing the table | ||
612 | lua_pushlightuserdata(L2, const_cast<void*>(p)); // L1: ... t ... L2: ... p | ||
613 | |||
614 | // fprintf(stderr, "<< ID: %s >>\n", lua_tostring(L2, -1)); | ||
615 | |||
616 | lua_rawget(L2, L2_cache_i); // L1: ... t ... L2: ... {cached|nil} | ||
617 | bool const not_found_in_cache{ lua_isnil(L2, -1) }; | ||
618 | if (not_found_in_cache) { | ||
619 | // create a new entry in the cache | ||
620 | lua_pop(L2, 1); // L1: ... t ... L2: ... | ||
621 | lua_newtable(L2); // L1: ... t ... L2: ... {} | ||
622 | lua_pushlightuserdata(L2, const_cast<void*>(p)); // L1: ... t ... L2: ... {} p | ||
623 | lua_pushvalue(L2, -2); // L1: ... t ... L2: ... {} p {} | ||
624 | lua_rawset(L2, L2_cache_i); // L1: ... t ... L2: ... {} | ||
625 | } | ||
626 | STACK_CHECK(L2, 1); | ||
627 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
628 | return !not_found_in_cache; | ||
629 | } | ||
630 | |||
631 | // ################################################################################################# | ||
632 | |||
633 | [[nodiscard]] bool InterCopyContext::tryCopyClonable() const | ||
634 | { | ||
635 | SourceIndex const L1i{ lua_absindex(L1, L1_i) }; | ||
636 | void* const source{ lua_touserdata(L1, L1i) }; | ||
637 | |||
638 | STACK_CHECK_START_REL(L1, 0); | ||
639 | STACK_CHECK_START_REL(L2, 0); | ||
640 | |||
641 | // Check if the source was already cloned during this copy | ||
642 | lua_pushlightuserdata(L2, source); // L2: ... source | ||
643 | lua_rawget(L2, L2_cache_i); // L2: ... clone? | ||
644 | if (!lua_isnil(L2, -1)) { | ||
645 | STACK_CHECK(L2, 1); | ||
646 | return true; | ||
647 | } else { | ||
648 | lua_pop(L2, 1); // L2: ... | ||
649 | } | ||
650 | STACK_CHECK(L2, 0); | ||
651 | |||
652 | // no metatable? -> not clonable | ||
653 | if (!lua_getmetatable(L1, L1i)) { // L1: ... mt? | ||
654 | STACK_CHECK(L1, 0); | ||
655 | return false; | ||
656 | } | ||
657 | |||
658 | // no __lanesclone? -> not clonable | ||
659 | lua_getfield(L1, -1, "__lanesclone"); // L1: ... mt __lanesclone? | ||
660 | if (lua_isnil(L1, -1)) { | ||
661 | lua_pop(L1, 2); // L1: ... | ||
662 | STACK_CHECK(L1, 0); | ||
663 | return false; | ||
664 | } | ||
665 | |||
666 | // we need to copy over the uservalues of the userdata as well | ||
667 | { | ||
668 | int const mt{ lua_absindex(L1, -2) }; // L1: ... mt __lanesclone | ||
669 | size_t const userdata_size{ lua_rawlen(L1, L1i) }; | ||
670 | // extract all the uservalues, but don't transfer them yet | ||
671 | int uvi = 0; | ||
672 | while (lua_getiuservalue(L1, L1i, ++uvi) != LUA_TNONE) {} // L1: ... mt __lanesclone [uv]+ nil | ||
673 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now | ||
674 | lua_pop(L1, 1); // L1: ... mt __lanesclone [uv]+ | ||
675 | --uvi; | ||
676 | // create the clone userdata with the required number of uservalue slots | ||
677 | void* const clone{ lua_newuserdatauv(L2, userdata_size, uvi) }; // L2: ... u | ||
678 | // copy the metatable in the target state, and give it to the clone we put there | ||
679 | InterCopyContext c{ U, L2, L1, L2_cache_i, SourceIndex{ mt }, VT::NORMAL, mode, name }; | ||
680 | if (c.inter_copy_one()) { // L2: ... u mt|sentinel | ||
681 | if (LookupMode::ToKeeper == mode) { // L2: ... u sentinel | ||
682 | LUA_ASSERT(L1, lua_tocfunction(L2, -1) == table_lookup_sentinel); | ||
683 | // we want to create a new closure with a 'clone sentinel' function, where the upvalues are the userdata and the metatable fqn | ||
684 | lua_getupvalue(L2, -1, 1); // L2: ... u sentinel fqn | ||
685 | lua_remove(L2, -2); // L2: ... u fqn | ||
686 | lua_insert(L2, -2); // L2: ... fqn u | ||
687 | lua_pushcclosure(L2, userdata_clone_sentinel, 2); // L2: ... userdata_clone_sentinel | ||
688 | } else { // from keeper or direct // L2: ... u mt | ||
689 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
690 | lua_setmetatable(L2, -2); // L2: ... u | ||
691 | } | ||
692 | STACK_CHECK(L2, 1); | ||
693 | } else { | ||
694 | raise_luaL_error(getErrL(), "Error copying a metatable"); | ||
695 | } | ||
696 | // first, add the entry in the cache (at this point it is either the actual userdata or the keeper sentinel | ||
697 | lua_pushlightuserdata(L2, source); // L2: ... u source | ||
698 | lua_pushvalue(L2, -2); // L2: ... u source u | ||
699 | lua_rawset(L2, L2_cache_i); // L2: ... u | ||
700 | // make sure we have the userdata now | ||
701 | if (LookupMode::ToKeeper == mode) { // L2: ... userdata_clone_sentinel | ||
702 | lua_getupvalue(L2, -1, 2); // L2: ... userdata_clone_sentinel u | ||
703 | } | ||
704 | // assign uservalues | ||
705 | while (uvi > 0) { | ||
706 | c.L1_i = SourceIndex{ lua_absindex(L1, -1) }; | ||
707 | if (!c.inter_copy_one()) { // L2: ... u uv | ||
708 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
709 | } | ||
710 | lua_pop(L1, 1); // L1: ... mt __lanesclone [uv]* | ||
711 | // this pops the value from the stack | ||
712 | lua_setiuservalue(L2, -2, uvi); // L2: ... u | ||
713 | --uvi; | ||
714 | } | ||
715 | // when we are done, all uservalues are popped from the source stack, and we want only the single transferred value in the destination | ||
716 | if (LookupMode::ToKeeper == mode) { // L2: ... userdata_clone_sentinel u | ||
717 | lua_pop(L2, 1); // L2: ... userdata_clone_sentinel | ||
718 | } | ||
719 | STACK_CHECK(L2, 1); | ||
720 | STACK_CHECK(L1, 2); | ||
721 | // call cloning function in source state to perform the actual memory cloning | ||
722 | lua_pushlightuserdata(L1, clone); // L1: ... mt __lanesclone clone | ||
723 | lua_pushlightuserdata(L1, source); // L1: ... mt __lanesclone clone source | ||
724 | lua_pushinteger(L1, static_cast<lua_Integer>(userdata_size)); // L1: ... mt __lanesclone clone source size | ||
725 | lua_call(L1, 3, 0); // L1: ... mt | ||
726 | STACK_CHECK(L1, 1); | ||
727 | } | ||
728 | |||
729 | STACK_CHECK(L2, 1); | ||
730 | lua_pop(L1, 1); // L1: ... | ||
731 | STACK_CHECK(L1, 0); | ||
732 | return true; | ||
733 | } | ||
734 | |||
735 | // ################################################################################################# | ||
736 | |||
737 | // Copy deep userdata between two separate Lua states (from L1 to L2) | ||
738 | // Returns false if not a deep userdata, else true (unless an error occured) | ||
739 | [[nodiscard]] bool InterCopyContext::tryCopyDeep() const | ||
740 | { | ||
741 | DeepFactory* const factory{ LookupFactory(L1, L1_i, mode) }; | ||
742 | if (factory == nullptr) { | ||
743 | return false; // not a deep userdata | ||
744 | } | ||
745 | |||
746 | STACK_CHECK_START_REL(L1, 0); | ||
747 | STACK_CHECK_START_REL(L2, 0); | ||
748 | |||
749 | // extract all uservalues of the source. unfortunately, the only way to know their count is to iterate until we fail | ||
750 | int nuv = 0; | ||
751 | while (lua_getiuservalue(L1, L1_i, nuv + 1) != LUA_TNONE) { // L1: ... u [uv]* nil | ||
752 | ++nuv; | ||
753 | } | ||
754 | // last call returned TNONE and pushed nil, that we don't need | ||
755 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
756 | STACK_CHECK(L1, nuv); | ||
757 | |||
758 | DeepPrelude* const u{ *lua_tofulluserdata<DeepPrelude*>(L1, L1_i) }; | ||
759 | char const* errmsg{ DeepFactory::PushDeepProxy(L2, u, nuv, mode) }; // L1: ... u [uv]* L2: u | ||
760 | if (errmsg != nullptr) { | ||
761 | raise_luaL_error(getErrL(), errmsg); | ||
762 | } | ||
763 | |||
764 | // transfer all uservalues of the source in the destination | ||
765 | { | ||
766 | InterCopyContext c{ U, L2, L1, L2_cache_i, {}, VT::NORMAL, mode, name }; | ||
767 | int const clone_i{ lua_gettop(L2) }; | ||
768 | while (nuv) { | ||
769 | c.L1_i = SourceIndex{ lua_absindex(L1, -1) }; | ||
770 | if (!c.inter_copy_one()) { // L1: ... u [uv]* L2: u uv | ||
771 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
772 | } | ||
773 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
774 | // this pops the value from the stack | ||
775 | lua_setiuservalue(L2, clone_i, nuv); // L2: u | ||
776 | --nuv; | ||
777 | } | ||
778 | } | ||
779 | |||
780 | STACK_CHECK(L2, 1); | ||
781 | STACK_CHECK(L1, 0); | ||
782 | |||
783 | return true; | ||
784 | } | ||
785 | |||
786 | // ################################################################################################# | ||
787 | |||
788 | [[nodiscard]] bool InterCopyContext::inter_copy_boolean() const | ||
789 | { | ||
790 | int const v{ lua_toboolean(L1, L1_i) }; | ||
791 | DEBUGSPEW_CODE(fprintf(stderr, "%s\n", v ? "true" : "false")); | ||
792 | lua_pushboolean(L2, v); | ||
793 | return true; | ||
794 | } | ||
795 | |||
796 | // ################################################################################################# | ||
797 | |||
798 | [[nodiscard]] bool InterCopyContext::inter_copy_function() const | ||
799 | { | ||
800 | if (vt == VT::KEY) { | ||
801 | return false; | ||
802 | } | ||
803 | |||
804 | STACK_CHECK_START_REL(L1, 0); | ||
805 | STACK_CHECK_START_REL(L2, 0); | ||
806 | DEBUGSPEW_CODE(fprintf(stderr, "FUNCTION %s\n", name)); | ||
807 | |||
808 | if (lua_tocfunction(L1, L1_i) == userdata_clone_sentinel) { // we are actually copying a clonable full userdata from a keeper | ||
809 | // clone the full userdata again | ||
810 | |||
811 | // let's see if we already restored this userdata | ||
812 | lua_getupvalue(L1, L1_i, 2); // L1: ... u | ||
813 | void* source = lua_touserdata(L1, -1); | ||
814 | lua_pushlightuserdata(L2, source); // L2: ... source | ||
815 | lua_rawget(L2, L2_cache_i); // L2: ... u? | ||
816 | if (!lua_isnil(L2, -1)) { | ||
817 | lua_pop(L1, 1); // L1: ... | ||
818 | STACK_CHECK(L1, 0); | ||
819 | STACK_CHECK(L2, 1); | ||
820 | return true; | ||
821 | } | ||
822 | lua_pop(L2, 1); // L2: ... | ||
823 | |||
824 | // userdata_clone_sentinel has 2 upvalues: the fqn of its metatable, and the userdata itself | ||
825 | bool const found{ lookup_table() }; // L2: ... mt? | ||
826 | if (!found) { | ||
827 | STACK_CHECK(L2, 0); | ||
828 | return false; | ||
829 | } | ||
830 | // 'L1_i' slot was the proxy closure, but from now on we operate onthe actual userdata we extracted from it | ||
831 | SourceIndex const source_i{ lua_gettop(L1) }; | ||
832 | source = lua_touserdata(L1, -1); | ||
833 | void* clone{ nullptr }; | ||
834 | // get the number of bytes to allocate for the clone | ||
835 | size_t const userdata_size{ lua_rawlen(L1, -1) }; | ||
836 | { | ||
837 | // extract uservalues (don't transfer them yet) | ||
838 | int uvi = 0; | ||
839 | while (lua_getiuservalue(L1, source_i, ++uvi) != LUA_TNONE) {} // L1: ... u uv | ||
840 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now | ||
841 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
842 | --uvi; | ||
843 | STACK_CHECK(L1, uvi + 1); | ||
844 | // create the clone userdata with the required number of uservalue slots | ||
845 | clone = lua_newuserdatauv(L2, userdata_size, uvi); // L2: ... mt u | ||
846 | // add it in the cache | ||
847 | lua_pushlightuserdata(L2, source); // L2: ... mt u source | ||
848 | lua_pushvalue(L2, -2); // L2: ... mt u source u | ||
849 | lua_rawset(L2, L2_cache_i); // L2: ... mt u | ||
850 | // set metatable | ||
851 | lua_pushvalue(L2, -2); // L2: ... mt u mt | ||
852 | lua_setmetatable(L2, -2); // L2: ... mt u | ||
853 | // transfer and assign uservalues | ||
854 | InterCopyContext c{ *this }; | ||
855 | while (uvi > 0) { | ||
856 | c.L1_i = SourceIndex{ lua_absindex(L1, -1) }; | ||
857 | if (!c.inter_copy_one()) { // L2: ... mt u uv | ||
858 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
859 | } | ||
860 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
861 | // this pops the value from the stack | ||
862 | lua_setiuservalue(L2, -2, uvi); // L2: ... mt u | ||
863 | --uvi; | ||
864 | } | ||
865 | // when we are done, all uservalues are popped from the stack, we can pop the source as well | ||
866 | lua_pop(L1, 1); // L1: ... | ||
867 | STACK_CHECK(L1, 0); | ||
868 | STACK_CHECK(L2, 2); // L2: ... mt u | ||
869 | } | ||
870 | // perform the custom cloning part | ||
871 | lua_insert(L2, -2); // L2: ... u mt | ||
872 | // __lanesclone should always exist because we wouldn't be restoring data from a userdata_clone_sentinel closure to begin with | ||
873 | lua_getfield(L2, -1, "__lanesclone"); // L2: ... u mt __lanesclone | ||
874 | lua_remove(L2, -2); // L2: ... u __lanesclone | ||
875 | lua_pushlightuserdata(L2, clone); // L2: ... u __lanesclone clone | ||
876 | lua_pushlightuserdata(L2, source); // L2: ... u __lanesclone clone source | ||
877 | lua_pushinteger(L2, userdata_size); // L2: ... u __lanesclone clone source size | ||
878 | // clone:__lanesclone(dest, source, size) | ||
879 | lua_call(L2, 3, 0); // L2: ... u | ||
880 | } else { // regular function | ||
881 | DEBUGSPEW_CODE(fprintf(stderr, "FUNCTION %s\n", name)); | ||
882 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
883 | copy_cached_func(); // L2: ... f | ||
884 | } | ||
885 | STACK_CHECK(L2, 1); | ||
886 | STACK_CHECK(L1, 0); | ||
887 | return true; | ||
888 | } | ||
889 | |||
890 | // ################################################################################################# | ||
891 | |||
892 | [[nodiscard]] bool InterCopyContext::inter_copy_lightuserdata() const | ||
893 | { | ||
894 | void* const p{ lua_touserdata(L1, L1_i) }; | ||
895 | DEBUGSPEW_CODE(fprintf(stderr, "%p\n", p)); | ||
896 | lua_pushlightuserdata(L2, p); | ||
897 | return true; | ||
898 | } | ||
899 | |||
900 | // ################################################################################################# | ||
901 | |||
902 | [[nodiscard]] bool InterCopyContext::inter_copy_nil() const | ||
903 | { | ||
904 | if (vt == VT::KEY) { | ||
905 | return false; | ||
906 | } | ||
907 | lua_pushnil(L2); | ||
908 | return true; | ||
909 | } | ||
910 | |||
911 | // ################################################################################################# | ||
912 | |||
913 | [[nodiscard]] bool InterCopyContext::inter_copy_number() const | ||
914 | { | ||
915 | // LNUM patch support (keeping integer accuracy) | ||
916 | #if defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
917 | if (lua_isinteger(L1, L1_i)) { | ||
918 | lua_Integer const v{ lua_tointeger(L1, L1_i) }; | ||
919 | DEBUGSPEW_CODE(fprintf(stderr, LUA_INTEGER_FMT "\n", v)); | ||
920 | lua_pushinteger(L2, v); | ||
921 | } else | ||
922 | #endif // defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
923 | { | ||
924 | lua_Number const v{ lua_tonumber(L1, L1_i) }; | ||
925 | DEBUGSPEW_CODE(fprintf(stderr, LUA_NUMBER_FMT "\n", v)); | ||
926 | lua_pushnumber(L2, v); | ||
927 | } | ||
928 | return true; | ||
929 | } | ||
930 | |||
931 | // ################################################################################################# | ||
932 | |||
933 | [[nodiscard]] bool InterCopyContext::inter_copy_string() const | ||
934 | { | ||
935 | size_t len; | ||
936 | char const* const s{ lua_tolstring(L1, L1_i, &len) }; | ||
937 | DEBUGSPEW_CODE(fprintf(stderr, "'%s'\n", s)); | ||
938 | lua_pushlstring(L2, s, len); | ||
939 | return true; | ||
940 | } | ||
941 | |||
942 | // ################################################################################################# | ||
943 | |||
944 | [[nodiscard]] bool InterCopyContext::inter_copy_table() const | ||
945 | { | ||
946 | if (vt == VT::KEY) { | ||
947 | return false; | ||
948 | } | ||
949 | |||
950 | STACK_CHECK_START_REL(L1, 0); | ||
951 | STACK_CHECK_START_REL(L2, 0); | ||
952 | DEBUGSPEW_CODE(fprintf(stderr, "TABLE %s\n", name)); | ||
953 | |||
954 | /* | ||
955 | * First, let's try to see if this table is special (aka is it some table that we registered in our lookup databases during module registration?) | ||
956 | * Note that this table CAN be a module table, but we just didn't register it, in which case we'll send it through the table cloning mechanism | ||
957 | */ | ||
958 | if (lookup_table()) { | ||
959 | LUA_ASSERT(L1, lua_istable(L2, -1) || (lua_tocfunction(L2, -1) == table_lookup_sentinel)); // from lookup data. can also be table_lookup_sentinel if this is a table we know | ||
960 | return true; | ||
961 | } | ||
962 | |||
963 | /* Check if we've already copied the same table from 'L1' (during this transmission), and | ||
964 | * reuse the old copy. This allows table upvalues shared by multiple | ||
965 | * local functions to point to the same table, also in the target. | ||
966 | * Also, this takes care of cyclic tables and multiple references | ||
967 | * to the same subtable. | ||
968 | * | ||
969 | * Note: Even metatables need to go through this test; to detect | ||
970 | * loops such as those in required module tables (getmetatable(lanes).lanes == lanes) | ||
971 | */ | ||
972 | if (push_cached_table()) { | ||
973 | LUA_ASSERT(L1, lua_istable(L2, -1)); // from cache | ||
974 | return true; | ||
975 | } | ||
976 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
977 | |||
978 | STACK_GROW(L1, 2); | ||
979 | STACK_GROW(L2, 2); | ||
980 | |||
981 | lua_pushnil(L1); // start iteration | ||
982 | while (lua_next(L1, L1_i)) { | ||
983 | // need a function to prevent overflowing the stack with verboseErrors-induced alloca() | ||
984 | inter_copy_keyvaluepair(); | ||
985 | lua_pop(L1, 1); // pop value (next round) | ||
986 | } | ||
987 | STACK_CHECK(L1, 0); | ||
988 | STACK_CHECK(L2, 1); | ||
989 | |||
990 | // Metatables are expected to be immutable, and copied only once. | ||
991 | if (push_cached_metatable()) { // L2: ... t mt? | ||
992 | lua_setmetatable(L2, -2); // L2: ... t | ||
993 | } | ||
994 | STACK_CHECK(L2, 1); | ||
995 | STACK_CHECK(L1, 0); | ||
996 | return true; | ||
997 | } | ||
998 | |||
999 | // ################################################################################################# | ||
1000 | |||
1001 | [[nodiscard]] bool InterCopyContext::inter_copy_userdata() const | ||
1002 | { | ||
1003 | STACK_CHECK_START_REL(L1, 0); | ||
1004 | STACK_CHECK_START_REL(L2, 0); | ||
1005 | if (vt == VT::KEY) { | ||
1006 | return false; | ||
1007 | } | ||
1008 | |||
1009 | // try clonable userdata first | ||
1010 | if (tryCopyClonable()) { | ||
1011 | STACK_CHECK(L1, 0); | ||
1012 | STACK_CHECK(L2, 1); | ||
1013 | return true; | ||
1014 | } | ||
1015 | |||
1016 | STACK_CHECK(L1, 0); | ||
1017 | STACK_CHECK(L2, 0); | ||
1018 | |||
1019 | // Allow only deep userdata entities to be copied across | ||
1020 | DEBUGSPEW_CODE(fprintf(stderr, "USERDATA\n")); | ||
1021 | if (tryCopyDeep()) { | ||
1022 | STACK_CHECK(L1, 0); | ||
1023 | STACK_CHECK(L2, 1); | ||
1024 | return true; | ||
1025 | } | ||
1026 | |||
1027 | STACK_CHECK(L1, 0); | ||
1028 | STACK_CHECK(L2, 0); | ||
1029 | |||
1030 | // Not a deep or clonable full userdata | ||
1031 | if (U->demoteFullUserdata) { // attempt demotion to light userdata | ||
1032 | void* const lud{ lua_touserdata(L1, L1_i) }; | ||
1033 | lua_pushlightuserdata(L2, lud); | ||
1034 | } else { // raise an error | ||
1035 | raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes"); | ||
1036 | } | ||
1037 | |||
1038 | STACK_CHECK(L2, 1); | ||
1039 | STACK_CHECK(L1, 0); | ||
1040 | return true; | ||
1041 | } | ||
1042 | |||
1043 | // ################################################################################################# | ||
1044 | |||
1045 | #if USE_DEBUG_SPEW() | ||
1046 | static char const* lua_type_names[] = { | ||
1047 | "LUA_TNIL" | ||
1048 | , "LUA_TBOOLEAN" | ||
1049 | , "LUA_TLIGHTUSERDATA" | ||
1050 | , "LUA_TNUMBER" | ||
1051 | , "LUA_TSTRING" | ||
1052 | , "LUA_TTABLE" | ||
1053 | , "LUA_TFUNCTION" | ||
1054 | , "LUA_TUSERDATA" | ||
1055 | , "LUA_TTHREAD" | ||
1056 | , "<LUA_NUMTAGS>" // not really a type | ||
1057 | , "LUA_TJITCDATA" // LuaJIT specific | ||
1058 | }; | ||
1059 | static char const* vt_names[] = { | ||
1060 | "VT::NORMAL" | ||
1061 | , "VT::KEY" | ||
1062 | , "VT::METATABLE" | ||
1063 | }; | ||
1064 | #endif // USE_DEBUG_SPEW() | ||
1065 | |||
1066 | /* | ||
1067 | * Copies a value from 'L1' state (at index 'i') to 'L2' state. Does not remove | ||
1068 | * the original value. | ||
1069 | * | ||
1070 | * NOTE: Both the states must be solely in the current OS thread's possession. | ||
1071 | * | ||
1072 | * 'i' is an absolute index (no -1, ...) | ||
1073 | * | ||
1074 | * Returns true if value was pushed, false if its type is non-supported. | ||
1075 | */ | ||
1076 | [[nodiscard]] bool InterCopyContext::inter_copy_one() const | ||
1077 | { | ||
1078 | static constexpr int kPODmask = (1 << LUA_TNIL) | (1 << LUA_TBOOLEAN) | (1 << LUA_TLIGHTUSERDATA) | (1 << LUA_TNUMBER) | (1 << LUA_TSTRING); | ||
1079 | STACK_GROW(L2, 1); | ||
1080 | STACK_CHECK_START_REL(L1, 0); | ||
1081 | STACK_CHECK_START_REL(L2, 0); | ||
1082 | |||
1083 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "inter_copy_one()\n" INDENT_END(U))); | ||
1084 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
1085 | |||
1086 | LuaType val_type{ lua_type_as_enum(L1, L1_i) }; | ||
1087 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "%s %s: " INDENT_END(U), lua_type_names[static_cast<int>(val_type)], vt_names[static_cast<int>(vt)])); | ||
1088 | |||
1089 | // Non-POD can be skipped if its metatable contains { __lanesignore = true } | ||
1090 | if (((1 << static_cast<int>(val_type)) & kPODmask) == 0) { | ||
1091 | if (lua_getmetatable(L1, L1_i)) { // L1: ... mt | ||
1092 | lua_getfield(L1, -1, "__lanesignore"); // L1: ... mt ignore? | ||
1093 | if (lua_isboolean(L1, -1) && lua_toboolean(L1, -1)) { | ||
1094 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "__lanesignore -> LUA_TNIL\n" INDENT_END(U))); | ||
1095 | val_type = LuaType::NIL; | ||
1096 | } | ||
1097 | lua_pop(L1, 2); // L1: ... | ||
1098 | } | ||
1099 | } | ||
1100 | STACK_CHECK(L1, 0); | ||
1101 | |||
1102 | // Lets push nil to L2 if the object should be ignored | ||
1103 | bool ret{ true }; | ||
1104 | switch (val_type) { | ||
1105 | // Basic types allowed both as values, and as table keys | ||
1106 | case LuaType::BOOLEAN: | ||
1107 | ret = inter_copy_boolean(); | ||
1108 | break; | ||
1109 | case LuaType::NUMBER: | ||
1110 | ret = inter_copy_number(); | ||
1111 | break; | ||
1112 | case LuaType::STRING: | ||
1113 | ret = inter_copy_string(); | ||
1114 | break; | ||
1115 | case LuaType::LIGHTUSERDATA: | ||
1116 | ret = inter_copy_lightuserdata(); | ||
1117 | break; | ||
1118 | |||
1119 | // The following types are not allowed as table keys | ||
1120 | case LuaType::USERDATA: | ||
1121 | ret = inter_copy_userdata(); | ||
1122 | break; | ||
1123 | case LuaType::NIL: | ||
1124 | ret = inter_copy_nil(); | ||
1125 | break; | ||
1126 | case LuaType::FUNCTION: | ||
1127 | ret = inter_copy_function(); | ||
1128 | break; | ||
1129 | case LuaType::TABLE: | ||
1130 | ret = inter_copy_table(); | ||
1131 | break; | ||
1132 | |||
1133 | // The following types cannot be copied | ||
1134 | case LuaType::CDATA: | ||
1135 | [[fallthrough]]; | ||
1136 | case LuaType::THREAD: | ||
1137 | ret = false; | ||
1138 | break; | ||
1139 | } | ||
1140 | |||
1141 | STACK_CHECK(L2, ret ? 1 : 0); | ||
1142 | STACK_CHECK(L1, 0); | ||
1143 | return ret; | ||
1144 | } | ||
1145 | |||
1146 | // ################################################################################################# | ||
1147 | |||
1148 | // transfers stuff from L1->_G["package"] to L2->_G["package"] | ||
1149 | // returns InterCopyResult::Success if everything is fine | ||
1150 | // returns InterCopyResult::Error if pushed an error message in L1 | ||
1151 | // else raise an error in L1 | ||
1152 | [[nodiscard]] InterCopyResult InterCopyContext::inter_copy_package() const | ||
1153 | { | ||
1154 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "InterCopyContext::inter_copy_package()\n" INDENT_END(U))); | ||
1155 | |||
1156 | class OnExit | ||
1157 | { | ||
1158 | private: | ||
1159 | lua_State* const L2; | ||
1160 | int const top_L2; | ||
1161 | DEBUGSPEW_CODE(DebugSpewIndentScope m_scope); | ||
1162 | |||
1163 | public: | ||
1164 | OnExit(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L2_) | ||
1165 | : L2{ L2_ } | ||
1166 | , top_L2{ lua_gettop(L2) } DEBUGSPEW_COMMA_PARAM(m_scope{ U_ }) | ||
1167 | { | ||
1168 | } | ||
1169 | |||
1170 | ~OnExit() | ||
1171 | { | ||
1172 | lua_settop(L2, top_L2); | ||
1173 | } | ||
1174 | } onExit{ DEBUGSPEW_PARAM_COMMA(U) L2 }; | ||
1175 | |||
1176 | STACK_CHECK_START_REL(L1, 0); | ||
1177 | if (lua_type_as_enum(L1, L1_i) != LuaType::TABLE) { | ||
1178 | lua_pushfstring(L1, "expected package as table, got %s", luaL_typename(L1, L1_i)); | ||
1179 | STACK_CHECK(L1, 1); | ||
1180 | // raise the error when copying from lane to lane, else just leave it on the stack to be raised later | ||
1181 | if (mode == LookupMode::LaneBody) { | ||
1182 | raise_lua_error(getErrL()); // that's ok, getErrL() is L1 in that case | ||
1183 | } | ||
1184 | return InterCopyResult::Error; | ||
1185 | } | ||
1186 | if (luaG_getmodule(L2, LUA_LOADLIBNAME) == LuaType::NIL) { // package library not loaded: do nothing | ||
1187 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "'package' not loaded, nothing to do\n" INDENT_END(U))); | ||
1188 | STACK_CHECK(L1, 0); | ||
1189 | return InterCopyResult::Success; | ||
1190 | } | ||
1191 | |||
1192 | InterCopyResult result{ InterCopyResult::Success }; | ||
1193 | // package.loaders is renamed package.searchers in Lua 5.2 | ||
1194 | // but don't copy it anyway, as the function names change depending on the slot index! | ||
1195 | // users should provide an on_state_create function to setup custom loaders instead | ||
1196 | // don't copy package.preload in keeper states (they don't know how to translate functions) | ||
1197 | char const* entries[] = { "path", "cpath", (mode == LookupMode::LaneBody) ? "preload" : nullptr /*, (LUA_VERSION_NUM == 501) ? "loaders" : "searchers"*/, nullptr }; | ||
1198 | for (char const* const entry : entries) { | ||
1199 | if (!entry) { | ||
1200 | continue; | ||
1201 | } | ||
1202 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "package.%s\n" INDENT_END(U), entry)); | ||
1203 | lua_getfield(L1, L1_i, entry); | ||
1204 | if (lua_isnil(L1, -1)) { | ||
1205 | lua_pop(L1, 1); | ||
1206 | } else { | ||
1207 | { | ||
1208 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
1209 | result = inter_move(1); // moves the entry to L2 | ||
1210 | STACK_CHECK(L1, 0); | ||
1211 | } | ||
1212 | if (result == InterCopyResult::Success) { | ||
1213 | lua_setfield(L2, -2, entry); // set package[entry] | ||
1214 | } else { | ||
1215 | lua_pushfstring(L1, "failed to copy package entry %s", entry); | ||
1216 | // raise the error when copying from lane to lane, else just leave it on the stack to be raised later | ||
1217 | if (mode == LookupMode::LaneBody) { | ||
1218 | raise_lua_error(getErrL()); | ||
1219 | } | ||
1220 | lua_pop(L1, 1); | ||
1221 | break; | ||
1222 | } | ||
1223 | } | ||
1224 | } | ||
1225 | STACK_CHECK(L1, 0); | ||
1226 | return result; | ||
1227 | } | ||
1228 | |||
1229 | // ################################################################################################# | ||
1230 | |||
1231 | // Akin to 'lua_xmove' but copies values between _any_ Lua states. | ||
1232 | // NOTE: Both the states must be solely in the current OS thread's possession. | ||
1233 | [[nodiscard]] InterCopyResult InterCopyContext::inter_copy(int n_) const | ||
1234 | { | ||
1235 | LUA_ASSERT(L1, vt == VT::NORMAL); | ||
1236 | |||
1237 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "InterCopyContext::inter_copy()\n" INDENT_END(U))); | ||
1238 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
1239 | |||
1240 | int const top_L1{ lua_gettop(L1) }; | ||
1241 | if (n_ > top_L1) { | ||
1242 | // requesting to copy more than is available? | ||
1243 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "nothing to copy()\n" INDENT_END(U))); | ||
1244 | return InterCopyResult::NotEnoughValues; | ||
1245 | } | ||
1246 | |||
1247 | STACK_CHECK_START_REL(L2, 0); | ||
1248 | STACK_GROW(L2, n_ + 1); | ||
1249 | |||
1250 | /* | ||
1251 | * Make a cache table for the duration of this copy. Collects tables and | ||
1252 | * function entries, avoiding the same entries to be passed on as multiple | ||
1253 | * copies. ESSENTIAL i.e. for handling upvalue tables in the right manner! | ||
1254 | */ | ||
1255 | int const top_L2{ lua_gettop(L2) }; // L2: ... | ||
1256 | lua_newtable(L2); // L2: ... cache | ||
1257 | |||
1258 | char tmpBuf[16]; | ||
1259 | char const* const pBuf{ U->verboseErrors ? tmpBuf : "?" }; | ||
1260 | InterCopyContext c{ U, L2, L1, CacheIndex{ top_L2 + 1 }, {}, VT::NORMAL, mode, pBuf }; | ||
1261 | bool copyok{ true }; | ||
1262 | STACK_CHECK_START_REL(L1, 0); | ||
1263 | for (int i{ top_L1 - n_ + 1 }, j{ 1 }; i <= top_L1; ++i, ++j) { | ||
1264 | if (U->verboseErrors) { | ||
1265 | sprintf(tmpBuf, "arg_%d", j); | ||
1266 | } | ||
1267 | c.L1_i = SourceIndex{ i }; | ||
1268 | copyok = c.inter_copy_one(); // L2: ... cache {}n | ||
1269 | if (!copyok) { | ||
1270 | break; | ||
1271 | } | ||
1272 | } | ||
1273 | STACK_CHECK(L1, 0); | ||
1274 | |||
1275 | if (copyok) { | ||
1276 | STACK_CHECK(L2, n_ + 1); | ||
1277 | // Remove the cache table. Persistent caching would cause i.e. multiple | ||
1278 | // messages passed in the same table to use the same table also in receiving end. | ||
1279 | lua_remove(L2, top_L2 + 1); | ||
1280 | return InterCopyResult::Success; | ||
1281 | } | ||
1282 | |||
1283 | // error -> pop everything from the target state stack | ||
1284 | lua_settop(L2, top_L2); | ||
1285 | STACK_CHECK(L2, 0); | ||
1286 | return InterCopyResult::Error; | ||
1287 | } | ||
1288 | |||
1289 | // ################################################################################################# | ||
1290 | |||
1291 | [[nodiscard]] InterCopyResult InterCopyContext::inter_move(int n_) const | ||
1292 | { | ||
1293 | InterCopyResult const ret{ inter_copy(n_) }; | ||
1294 | lua_pop(L1, n_); | ||
1295 | return ret; | ||
1296 | } | ||
diff --git a/src/intercopycontext.h b/src/intercopycontext.h new file mode 100644 index 0000000..28e1ead --- /dev/null +++ b/src/intercopycontext.h | |||
@@ -0,0 +1,77 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include "tools.h" | ||
4 | |||
5 | // forwards | ||
6 | class Universe; | ||
7 | |||
8 | // ################################################################################################# | ||
9 | |||
10 | enum class VT | ||
11 | { | ||
12 | NORMAL, // keep this one first so that it's the value we get when we default-construct | ||
13 | KEY, | ||
14 | METATABLE | ||
15 | }; | ||
16 | |||
17 | enum class InterCopyResult | ||
18 | { | ||
19 | Success, | ||
20 | NotEnoughValues, | ||
21 | Error | ||
22 | }; | ||
23 | |||
24 | // ################################################################################################# | ||
25 | |||
26 | using CacheIndex = Unique<int>; | ||
27 | using SourceIndex = Unique<int>; | ||
28 | class InterCopyContext | ||
29 | { | ||
30 | public: | ||
31 | Universe* const U; | ||
32 | DestState const L2; | ||
33 | SourceState const L1; | ||
34 | CacheIndex const L2_cache_i; | ||
35 | SourceIndex L1_i; // that one can change when we reuse the context | ||
36 | VT vt; // that one can change when we reuse the context | ||
37 | LookupMode const mode; | ||
38 | char const* name; // that one can change when we reuse the context | ||
39 | |||
40 | private: | ||
41 | // when mode == LookupMode::FromKeeper, L1 is a keeper state and L2 is not, therefore L2 is the state where we want to raise the error | ||
42 | // whon mode != LookupMode::FromKeeper, L1 is not a keeper state, therefore L1 is the state where we want to raise the error | ||
43 | lua_State* getErrL() const { return (mode == LookupMode::FromKeeper) ? L2 : L1; } | ||
44 | |||
45 | // for use in copy_cached_func | ||
46 | void copy_func() const; | ||
47 | void lookup_native_func() const; | ||
48 | |||
49 | // for use in inter_copy_function | ||
50 | void copy_cached_func() const; | ||
51 | [[nodiscard]] bool lookup_table() const; | ||
52 | |||
53 | // for use in inter_copy_table | ||
54 | void inter_copy_keyvaluepair() const; | ||
55 | [[nodiscard]] bool push_cached_metatable() const; | ||
56 | [[nodiscard]] bool push_cached_table() const; | ||
57 | |||
58 | // for use in inter_copy_userdata | ||
59 | [[nodiscard]] bool tryCopyClonable() const; | ||
60 | [[nodiscard]] bool tryCopyDeep() const; | ||
61 | |||
62 | // copying a single Lua stack item | ||
63 | [[nodiscard]] bool inter_copy_boolean() const; | ||
64 | [[nodiscard]] bool inter_copy_function() const; | ||
65 | [[nodiscard]] bool inter_copy_lightuserdata() const; | ||
66 | [[nodiscard]] bool inter_copy_nil() const; | ||
67 | [[nodiscard]] bool inter_copy_number() const; | ||
68 | [[nodiscard]] bool inter_copy_string() const; | ||
69 | [[nodiscard]] bool inter_copy_table() const; | ||
70 | [[nodiscard]] bool inter_copy_userdata() const; | ||
71 | |||
72 | public: | ||
73 | [[nodiscard]] bool inter_copy_one() const; | ||
74 | [[nodiscard]] InterCopyResult inter_copy_package() const; | ||
75 | [[nodiscard]] InterCopyResult inter_copy(int n_) const; | ||
76 | [[nodiscard]] InterCopyResult inter_move(int n_) const; | ||
77 | }; | ||
diff --git a/src/keeper.cpp b/src/keeper.cpp index bb510f4..7367d0c 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp | |||
@@ -39,12 +39,9 @@ | |||
39 | */ | 39 | */ |
40 | #include "keeper.h" | 40 | #include "keeper.h" |
41 | 41 | ||
42 | #include "compat.h" | 42 | #include "intercopycontext.h" |
43 | #include "linda.h" | 43 | #include "linda.h" |
44 | #include "state.h" | 44 | #include "state.h" |
45 | #include "tools.h" | ||
46 | #include "uniquekey.h" | ||
47 | #include "universe.h" | ||
48 | 45 | ||
49 | #include <algorithm> | 46 | #include <algorithm> |
50 | #include <cassert> | 47 | #include <cassert> |
diff --git a/src/keeper.h b/src/keeper.h index 37642fd..8f30720 100644 --- a/src/keeper.h +++ b/src/keeper.h | |||
@@ -9,7 +9,6 @@ extern "C" | |||
9 | } | 9 | } |
10 | #endif // __cplusplus | 10 | #endif // __cplusplus |
11 | 11 | ||
12 | #include "threading.h" | ||
13 | #include "uniquekey.h" | 12 | #include "uniquekey.h" |
14 | 13 | ||
15 | #include <optional> | 14 | #include <optional> |
diff --git a/src/lanes.cpp b/src/lanes.cpp index d211b6a..ee40ffa 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp | |||
@@ -81,7 +81,8 @@ THE SOFTWARE. | |||
81 | 81 | ||
82 | #include "lanes.h" | 82 | #include "lanes.h" |
83 | 83 | ||
84 | #include "compat.h" | 84 | #include "deep.h" |
85 | #include "intercopycontext.h" | ||
85 | #include "keeper.h" | 86 | #include "keeper.h" |
86 | #include "lanes_private.h" | 87 | #include "lanes_private.h" |
87 | #include "state.h" | 88 | #include "state.h" |
@@ -1522,6 +1523,84 @@ LUAG_FUNC(wakeup_conv) | |||
1522 | } | 1523 | } |
1523 | 1524 | ||
1524 | // ################################################################################################# | 1525 | // ################################################################################################# |
1526 | // ################################### custom allocator support #################################### | ||
1527 | // ################################################################################################# | ||
1528 | |||
1529 | // same as PUC-Lua l_alloc | ||
1530 | extern "C" [[nodiscard]] static void* libc_lua_Alloc([[maybe_unused]] void* ud, [[maybe_unused]] void* ptr_, [[maybe_unused]] size_t osize_, size_t nsize_) | ||
1531 | { | ||
1532 | if (nsize_ == 0) { | ||
1533 | free(ptr_); | ||
1534 | return nullptr; | ||
1535 | } else { | ||
1536 | return realloc(ptr_, nsize_); | ||
1537 | } | ||
1538 | } | ||
1539 | |||
1540 | // ################################################################################################# | ||
1541 | |||
1542 | [[nodiscard]] static int luaG_provide_protected_allocator(lua_State* L_) | ||
1543 | { | ||
1544 | Universe* const U{ universe_get(L_) }; | ||
1545 | // push a new full userdata on the stack, giving access to the universe's protected allocator | ||
1546 | [[maybe_unused]] AllocatorDefinition* const def{ new (L_) AllocatorDefinition{ U->protectedAllocator.makeDefinition() } }; | ||
1547 | return 1; | ||
1548 | } | ||
1549 | |||
1550 | // ################################################################################################# | ||
1551 | |||
1552 | // called once at the creation of the universe (therefore L is the master Lua state everything originates from) | ||
1553 | // Do I need to disable this when compiling for LuaJIT to prevent issues? | ||
1554 | static void initialize_allocator_function(Universe* U_, lua_State* L_) | ||
1555 | { | ||
1556 | STACK_CHECK_START_REL(L_, 1); // L_: settings | ||
1557 | lua_getfield(L_, -1, "allocator"); // L_: settings allocator|nil|"protected" | ||
1558 | if (!lua_isnil(L_, -1)) { | ||
1559 | // store C function pointer in an internal variable | ||
1560 | U_->provideAllocator = lua_tocfunction(L_, -1); // L_: settings allocator | ||
1561 | if (U_->provideAllocator != nullptr) { | ||
1562 | // make sure the function doesn't have upvalues | ||
1563 | char const* upname = lua_getupvalue(L_, -1, 1); // L_: settings allocator upval? | ||
1564 | if (upname != nullptr) { // should be "" for C functions with upvalues if any | ||
1565 | raise_luaL_error(L_, "config.allocator() shouldn't have upvalues"); | ||
1566 | } | ||
1567 | // remove this C function from the config table so that it doesn't cause problems | ||
1568 | // when we transfer the config table in newly created Lua states | ||
1569 | lua_pushnil(L_); // L_: settings allocator nil | ||
1570 | lua_setfield(L_, -3, "allocator"); // L_: settings allocator | ||
1571 | } else if (lua_type(L_, -1) == LUA_TSTRING) { // should be "protected" | ||
1572 | LUA_ASSERT(L_, strcmp(lua_tostring(L_, -1), "protected") == 0); | ||
1573 | // set the original allocator to call from inside protection by the mutex | ||
1574 | U_->protectedAllocator.initFrom(L_); | ||
1575 | U_->protectedAllocator.installIn(L_); | ||
1576 | // before a state is created, this function will be called to obtain the allocator | ||
1577 | U_->provideAllocator = luaG_provide_protected_allocator; | ||
1578 | } | ||
1579 | } else { | ||
1580 | // just grab whatever allocator was provided to lua_newstate | ||
1581 | U_->protectedAllocator.initFrom(L_); | ||
1582 | } | ||
1583 | lua_pop(L_, 1); // L_: settings | ||
1584 | STACK_CHECK(L_, 1); | ||
1585 | |||
1586 | lua_getfield(L_, -1, "internal_allocator"); // L_: settings "libc"|"allocator" | ||
1587 | { | ||
1588 | char const* allocator = lua_tostring(L_, -1); | ||
1589 | if (strcmp(allocator, "libc") == 0) { | ||
1590 | U_->internalAllocator = AllocatorDefinition{ libc_lua_Alloc, nullptr }; | ||
1591 | } else if (U_->provideAllocator == luaG_provide_protected_allocator) { | ||
1592 | // user wants mutex protection on the state's allocator. Use protection for our own allocations too, just in case. | ||
1593 | U_->internalAllocator = U_->protectedAllocator.makeDefinition(); | ||
1594 | } else { | ||
1595 | // no protection required, just use whatever we have as-is. | ||
1596 | U_->internalAllocator = U_->protectedAllocator; | ||
1597 | } | ||
1598 | } | ||
1599 | lua_pop(L_, 1); // L_: settings | ||
1600 | STACK_CHECK(L_, 1); | ||
1601 | } | ||
1602 | |||
1603 | // ################################################################################################# | ||
1525 | // ######################################## Module linkage ######################################### | 1604 | // ######################################## Module linkage ######################################### |
1526 | // ################################################################################################# | 1605 | // ################################################################################################# |
1527 | 1606 | ||
diff --git a/src/linda.cpp b/src/linda.cpp index 40ef6c7..07911b3 100644 --- a/src/linda.cpp +++ b/src/linda.cpp | |||
@@ -32,13 +32,9 @@ THE SOFTWARE. | |||
32 | 32 | ||
33 | #include "linda.h" | 33 | #include "linda.h" |
34 | 34 | ||
35 | #include "compat.h" | ||
36 | #include "keeper.h" | ||
37 | #include "lanes_private.h" | 35 | #include "lanes_private.h" |
38 | #include "lindafactory.h" | 36 | #include "lindafactory.h" |
39 | #include "threading.h" | ||
40 | #include "tools.h" | 37 | #include "tools.h" |
41 | #include "universe.h" | ||
42 | 38 | ||
43 | #include <functional> | 39 | #include <functional> |
44 | 40 | ||
diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp index 917d949..0ec5a0a 100644 --- a/src/lindafactory.cpp +++ b/src/lindafactory.cpp | |||
@@ -32,7 +32,6 @@ THE SOFTWARE. | |||
32 | 32 | ||
33 | #include "lindafactory.h" | 33 | #include "lindafactory.h" |
34 | 34 | ||
35 | #include "lanes_private.h" | ||
36 | #include "linda.h" | 35 | #include "linda.h" |
37 | 36 | ||
38 | // must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it) | 37 | // must be a #define instead of a constexpr to work with lua_pushliteral (until I templatize it) |
diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h index a1f6cba..b8f04b6 100644 --- a/src/macros_and_utils.h +++ b/src/macros_and_utils.h | |||
@@ -61,7 +61,7 @@ template <typename... ARGS> | |||
61 | 61 | ||
62 | // ################################################################################################# | 62 | // ################################################################################################# |
63 | 63 | ||
64 | #define USE_DEBUG_SPEW() 0 | 64 | #define USE_DEBUG_SPEW() 1 |
65 | #if USE_DEBUG_SPEW() | 65 | #if USE_DEBUG_SPEW() |
66 | #define INDENT_BEGIN "%.*s " | 66 | #define INDENT_BEGIN "%.*s " |
67 | #define INDENT_END(U_) , (U_ ? U_->debugspewIndentDepth.load(std::memory_order_relaxed) : 0), DebugSpewIndentScope::debugspew_indent | 67 | #define INDENT_END(U_) , (U_ ? U_->debugspewIndentDepth.load(std::memory_order_relaxed) : 0), DebugSpewIndentScope::debugspew_indent |
diff --git a/src/state.cpp b/src/state.cpp index f894978..7252885 100644 --- a/src/state.cpp +++ b/src/state.cpp | |||
@@ -33,6 +33,7 @@ THE SOFTWARE. | |||
33 | 33 | ||
34 | #include "state.h" | 34 | #include "state.h" |
35 | 35 | ||
36 | #include "intercopycontext.h" | ||
36 | #include "lanes.h" | 37 | #include "lanes.h" |
37 | #include "lanes_private.h" | 38 | #include "lanes_private.h" |
38 | #include "tools.h" | 39 | #include "tools.h" |
diff --git a/src/tools.cpp b/src/tools.cpp index 73efda9..2623da6 100644 --- a/src/tools.cpp +++ b/src/tools.cpp | |||
@@ -1,5 +1,6 @@ | |||
1 | /* | 1 | /* |
2 | * TOOLS.C Copyright (c) 2002-10, Asko Kauppi | 2 | * TOOLS.CPP Copyright (c) 2002-10, Asko Kauppi |
3 | * Copyright (C) 2010-24, Benoit Germain | ||
3 | * | 4 | * |
4 | * Lua tools to support Lanes. | 5 | * Lua tools to support Lanes. |
5 | */ | 6 | */ |
@@ -8,7 +9,7 @@ | |||
8 | =============================================================================== | 9 | =============================================================================== |
9 | 10 | ||
10 | Copyright (C) 2002-10 Asko Kauppi <akauppi@gmail.com> | 11 | Copyright (C) 2002-10 Asko Kauppi <akauppi@gmail.com> |
11 | 2011-17 benoit Germain <bnt.germain@gmail.com> | 12 | 2011-24 benoit Germain <bnt.germain@gmail.com> |
12 | 13 | ||
13 | Permission is hereby granted, free of charge, to any person obtaining a copy | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy |
14 | of this software and associated documentation files (the "Software"), to deal | 15 | of this software and associated documentation files (the "Software"), to deal |
@@ -42,82 +43,6 @@ static constexpr RegistryUniqueKey kLookupCacheRegKey{ 0x9BF75F84E54B691Bull }; | |||
42 | 43 | ||
43 | // ################################################################################################# | 44 | // ################################################################################################# |
44 | 45 | ||
45 | // same as PUC-Lua l_alloc | ||
46 | extern "C" [[nodiscard]] static void* libc_lua_Alloc([[maybe_unused]] void* ud, [[maybe_unused]] void* ptr_, [[maybe_unused]] size_t osize_, size_t nsize_) | ||
47 | { | ||
48 | if (nsize_ == 0) { | ||
49 | free(ptr_); | ||
50 | return nullptr; | ||
51 | } else { | ||
52 | return realloc(ptr_, nsize_); | ||
53 | } | ||
54 | } | ||
55 | |||
56 | // ################################################################################################# | ||
57 | |||
58 | [[nodiscard]] static int luaG_provide_protected_allocator(lua_State* L_) | ||
59 | { | ||
60 | Universe* const U{ universe_get(L_) }; | ||
61 | // push a new full userdata on the stack, giving access to the universe's protected allocator | ||
62 | [[maybe_unused]] AllocatorDefinition* const def{ new (L_) AllocatorDefinition{ U->protectedAllocator.makeDefinition() } }; | ||
63 | return 1; | ||
64 | } | ||
65 | |||
66 | // ################################################################################################# | ||
67 | |||
68 | // called once at the creation of the universe (therefore L is the master Lua state everything originates from) | ||
69 | // Do I need to disable this when compiling for LuaJIT to prevent issues? | ||
70 | void initialize_allocator_function(Universe* U_, lua_State* L_) | ||
71 | { | ||
72 | STACK_CHECK_START_REL(L_, 1); // L_: settings | ||
73 | lua_getfield(L_, -1, "allocator"); // L_: settings allocator|nil|"protected" | ||
74 | if (!lua_isnil(L_, -1)) { | ||
75 | // store C function pointer in an internal variable | ||
76 | U_->provideAllocator = lua_tocfunction(L_, -1); // L_: settings allocator | ||
77 | if (U_->provideAllocator != nullptr) { | ||
78 | // make sure the function doesn't have upvalues | ||
79 | char const* upname = lua_getupvalue(L_, -1, 1); // L_: settings allocator upval? | ||
80 | if (upname != nullptr) { // should be "" for C functions with upvalues if any | ||
81 | raise_luaL_error(L_, "config.allocator() shouldn't have upvalues"); | ||
82 | } | ||
83 | // remove this C function from the config table so that it doesn't cause problems | ||
84 | // when we transfer the config table in newly created Lua states | ||
85 | lua_pushnil(L_); // L_: settings allocator nil | ||
86 | lua_setfield(L_, -3, "allocator"); // L_: settings allocator | ||
87 | } else if (lua_type(L_, -1) == LUA_TSTRING) { // should be "protected" | ||
88 | LUA_ASSERT(L_, strcmp(lua_tostring(L_, -1), "protected") == 0); | ||
89 | // set the original allocator to call from inside protection by the mutex | ||
90 | U_->protectedAllocator.initFrom(L_); | ||
91 | U_->protectedAllocator.installIn(L_); | ||
92 | // before a state is created, this function will be called to obtain the allocator | ||
93 | U_->provideAllocator = luaG_provide_protected_allocator; | ||
94 | } | ||
95 | } else { | ||
96 | // just grab whatever allocator was provided to lua_newstate | ||
97 | U_->protectedAllocator.initFrom(L_); | ||
98 | } | ||
99 | lua_pop(L_, 1); // L_: settings | ||
100 | STACK_CHECK(L_, 1); | ||
101 | |||
102 | lua_getfield(L_, -1, "internal_allocator"); // L_: settings "libc"|"allocator" | ||
103 | { | ||
104 | char const* allocator = lua_tostring(L_, -1); | ||
105 | if (strcmp(allocator, "libc") == 0) { | ||
106 | U_->internalAllocator = AllocatorDefinition{ libc_lua_Alloc, nullptr }; | ||
107 | } else if (U_->provideAllocator == luaG_provide_protected_allocator) { | ||
108 | // user wants mutex protection on the state's allocator. Use protection for our own allocations too, just in case. | ||
109 | U_->internalAllocator = U_->protectedAllocator.makeDefinition(); | ||
110 | } else { | ||
111 | // no protection required, just use whatever we have as-is. | ||
112 | U_->internalAllocator = U_->protectedAllocator; | ||
113 | } | ||
114 | } | ||
115 | lua_pop(L_, 1); // L_: settings | ||
116 | STACK_CHECK(L_, 1); | ||
117 | } | ||
118 | |||
119 | // ################################################################################################# | ||
120 | |||
121 | [[nodiscard]] static int dummy_writer([[maybe_unused]] lua_State* L_, [[maybe_unused]] void const* p_, [[maybe_unused]] size_t sz_, [[maybe_unused]] void* ud_) | 46 | [[nodiscard]] static int dummy_writer([[maybe_unused]] lua_State* L_, [[maybe_unused]] void const* p_, [[maybe_unused]] size_t sz_, [[maybe_unused]] void* ud_) |
122 | { | 47 | { |
123 | return 666; | 48 | return 666; |
@@ -138,14 +63,7 @@ void initialize_allocator_function(Universe* U_, lua_State* L_) | |||
138 | * +-----------------+----------+------------+----------+ | 63 | * +-----------------+----------+------------+----------+ |
139 | */ | 64 | */ |
140 | 65 | ||
141 | enum class FuncSubType | 66 | [[nodiscard]] FuncSubType luaG_getfuncsubtype(lua_State* L_, int _i) |
142 | { | ||
143 | Bytecode, | ||
144 | Native, | ||
145 | FastJIT | ||
146 | }; | ||
147 | |||
148 | FuncSubType luaG_getfuncsubtype(lua_State* L_, int _i) | ||
149 | { | 67 | { |
150 | if (lua_tocfunction(L_, _i)) { // nullptr for LuaJIT-fast && bytecode functions | 68 | if (lua_tocfunction(L_, _i)) { // nullptr for LuaJIT-fast && bytecode functions |
151 | return FuncSubType::Native; | 69 | return FuncSubType::Native; |
@@ -430,227 +348,6 @@ void populate_func_lookup_table(lua_State* L_, int i_, char const* name_) | |||
430 | 348 | ||
431 | // ################################################################################################# | 349 | // ################################################################################################# |
432 | 350 | ||
433 | /*---=== Inter-state copying ===---*/ | ||
434 | |||
435 | // xxh64 of string "kMtIdRegKey" generated at https://www.pelock.com/products/hash-calculator | ||
436 | static constexpr RegistryUniqueKey kMtIdRegKey{ 0xA8895DCF4EC3FE3Cull }; | ||
437 | |||
438 | // get a unique ID for metatable at [i]. | ||
439 | [[nodiscard]] static lua_Integer get_mt_id(Universe* U_, lua_State* L_, int idx_) | ||
440 | { | ||
441 | idx_ = lua_absindex(L_, idx_); | ||
442 | |||
443 | STACK_GROW(L_, 3); | ||
444 | |||
445 | STACK_CHECK_START_REL(L_, 0); | ||
446 | std::ignore = kMtIdRegKey.getSubTable(L_, 0, 0); // L_: ... _R[kMtIdRegKey] | ||
447 | lua_pushvalue(L_, idx_); // L_: ... _R[kMtIdRegKey] {mt} | ||
448 | lua_rawget(L_, -2); // L_: ... _R[kMtIdRegKey] mtk? | ||
449 | |||
450 | lua_Integer id{ lua_tointeger(L_, -1) }; // 0 for nil | ||
451 | lua_pop(L_, 1); // L_: ... _R[kMtIdRegKey] | ||
452 | STACK_CHECK(L_, 1); | ||
453 | |||
454 | if (id == 0) { | ||
455 | id = U_->nextMetatableId.fetch_add(1, std::memory_order_relaxed); | ||
456 | |||
457 | // Create two-way references: id_uint <-> table | ||
458 | lua_pushvalue(L_, idx_); // L_: ... _R[kMtIdRegKey] {mt} | ||
459 | lua_pushinteger(L_, id); // L_: ... _R[kMtIdRegKey] {mt} id | ||
460 | lua_rawset(L_, -3); // L_: ... _R[kMtIdRegKey] | ||
461 | |||
462 | lua_pushinteger(L_, id); // L_: ... _R[kMtIdRegKey] id | ||
463 | lua_pushvalue(L_, idx_); // L_: ... _R[kMtIdRegKey] id {mt} | ||
464 | lua_rawset(L_, -3); // L_: ... _R[kMtIdRegKey] | ||
465 | } | ||
466 | lua_pop(L_, 1); // L_: ... | ||
467 | STACK_CHECK(L_, 0); | ||
468 | |||
469 | return id; | ||
470 | } | ||
471 | |||
472 | // ################################################################################################# | ||
473 | |||
474 | // function sentinel used to transfer native functions from/to keeper states | ||
475 | [[nodiscard]] static int func_lookup_sentinel(lua_State* L_) | ||
476 | { | ||
477 | raise_luaL_error(L_, "function lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | ||
478 | } | ||
479 | |||
480 | // ################################################################################################# | ||
481 | |||
482 | // function sentinel used to transfer native table from/to keeper states | ||
483 | [[nodiscard]] static int table_lookup_sentinel(lua_State* L_) | ||
484 | { | ||
485 | raise_luaL_error(L_, "table lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | ||
486 | } | ||
487 | |||
488 | // ################################################################################################# | ||
489 | |||
490 | // function sentinel used to transfer cloned full userdata from/to keeper states | ||
491 | [[nodiscard]] static int userdata_clone_sentinel(lua_State* L_) | ||
492 | { | ||
493 | raise_luaL_error(L_, "userdata clone sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); | ||
494 | } | ||
495 | |||
496 | // ################################################################################################# | ||
497 | |||
498 | // retrieve the name of a function/table in the lookup database | ||
499 | [[nodiscard]] static char const* find_lookup_name(lua_State* L_, int i_, LookupMode mode_, char const* upName_, size_t* len_) | ||
500 | { | ||
501 | LUA_ASSERT(L_, lua_isfunction(L_, i_) || lua_istable(L_, i_)); // L_: ... v ... | ||
502 | STACK_CHECK_START_REL(L_, 0); | ||
503 | STACK_GROW(L_, 3); // up to 3 slots are necessary on error | ||
504 | if (mode_ == LookupMode::FromKeeper) { | ||
505 | lua_CFunction f = lua_tocfunction(L_, i_); // should *always* be one of the function sentinels | ||
506 | if (f == func_lookup_sentinel || f == table_lookup_sentinel || f == userdata_clone_sentinel) { | ||
507 | lua_getupvalue(L_, i_, 1); // L_: ... v ... "f.q.n" | ||
508 | } else { | ||
509 | // if this is not a sentinel, this is some user-created table we wanted to lookup | ||
510 | LUA_ASSERT(L_, nullptr == f && lua_istable(L_, i_)); | ||
511 | // push anything that will convert to nullptr string | ||
512 | lua_pushnil(L_); // L_: ... v ... nil | ||
513 | } | ||
514 | } else { | ||
515 | // fetch the name from the source state's lookup table | ||
516 | kLookupRegKey.pushValue(L_); // L_: ... v ... {} | ||
517 | STACK_CHECK(L_, 1); | ||
518 | LUA_ASSERT(L_, lua_istable(L_, -1)); | ||
519 | lua_pushvalue(L_, i_); // L_: ... v ... {} v | ||
520 | lua_rawget(L_, -2); // L_: ... v ... {} "f.q.n" | ||
521 | } | ||
522 | char const* fqn{ lua_tolstring(L_, -1, len_) }; | ||
523 | DEBUGSPEW_CODE(Universe* const U = universe_get(L_)); | ||
524 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "function [C] %s \n" INDENT_END(U), fqn)); | ||
525 | // popping doesn't invalidate the pointer since this is an interned string gotten from the lookup database | ||
526 | lua_pop(L_, (mode_ == LookupMode::FromKeeper) ? 1 : 2); // L_: ... v ... | ||
527 | STACK_CHECK(L_, 0); | ||
528 | if (nullptr == fqn && !lua_istable(L_, i_)) { // raise an error if we try to send an unknown function (but not for tables) | ||
529 | *len_ = 0; // just in case | ||
530 | // try to discover the name of the function we want to send | ||
531 | lua_getglobal(L_, "decoda_name"); // L_: ... v ... decoda_name | ||
532 | char const* from{ lua_tostring(L_, -1) }; | ||
533 | lua_pushcfunction(L_, luaG_nameof); // L_: ... v ... decoda_name luaG_nameof | ||
534 | lua_pushvalue(L_, i_); // L_: ... v ... decoda_name luaG_nameof t | ||
535 | lua_call(L_, 1, 2); // L_: ... v ... decoda_name "type" "name"|nil | ||
536 | char const* typewhat{ (lua_type(L_, -2) == LUA_TSTRING) ? lua_tostring(L_, -2) : luaL_typename(L_, -2) }; | ||
537 | // second return value can be nil if the table was not found | ||
538 | // probable reason: the function was removed from the source Lua state before Lanes was required. | ||
539 | char const *what, *gotchaA, *gotchaB; | ||
540 | if (lua_isnil(L_, -1)) { | ||
541 | gotchaA = " referenced by"; | ||
542 | gotchaB = "\n(did you remove it from the source Lua state before requiring Lanes?)"; | ||
543 | what = upName_; | ||
544 | } else { | ||
545 | gotchaA = ""; | ||
546 | gotchaB = ""; | ||
547 | what = (lua_type(L_, -1) == LUA_TSTRING) ? lua_tostring(L_, -1) : luaL_typename(L_, -1); | ||
548 | } | ||
549 | raise_luaL_error(L_, "%s%s '%s' not found in %s origin transfer database.%s", typewhat, gotchaA, what, from ? from : "main", gotchaB); | ||
550 | } | ||
551 | STACK_CHECK(L_, 0); | ||
552 | return fqn; | ||
553 | } | ||
554 | |||
555 | // ################################################################################################# | ||
556 | |||
557 | // Push a looked-up table, or nothing if we found nothing | ||
558 | [[nodiscard]] bool InterCopyContext::lookup_table() const | ||
559 | { | ||
560 | // get the name of the table we want to send | ||
561 | size_t len; | ||
562 | char const* fqn = find_lookup_name(L1, L1_i, mode, name, &len); | ||
563 | if (nullptr == fqn) { // name not found, it is some user-created table | ||
564 | return false; | ||
565 | } | ||
566 | // push the equivalent table in the destination's stack, retrieved from the lookup table | ||
567 | STACK_CHECK_START_REL(L2, 0); | ||
568 | STACK_GROW(L2, 3); // up to 3 slots are necessary on error | ||
569 | switch (mode) { | ||
570 | default: // shouldn't happen, in theory... | ||
571 | raise_luaL_error(getErrL(), "internal error: unknown lookup mode"); | ||
572 | break; | ||
573 | |||
574 | case LookupMode::ToKeeper: | ||
575 | // push a sentinel closure that holds the lookup name as upvalue | ||
576 | lua_pushlstring(L2, fqn, len); // L1: ... t ... L2: "f.q.n" | ||
577 | lua_pushcclosure(L2, table_lookup_sentinel, 1); // L1: ... t ... L2: f | ||
578 | break; | ||
579 | |||
580 | case LookupMode::LaneBody: | ||
581 | case LookupMode::FromKeeper: | ||
582 | kLookupRegKey.pushValue(L2); // L1: ... t ... L2: {} | ||
583 | STACK_CHECK(L2, 1); | ||
584 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
585 | lua_pushlstring(L2, fqn, len); // L2: {} "f.q.n" | ||
586 | lua_rawget(L2, -2); // L2: {} t | ||
587 | // we accept destination lookup failures in the case of transfering the Lanes body function (this will result in the source table being cloned instead) | ||
588 | // but not when we extract something out of a keeper, as there is nothing to clone! | ||
589 | if (lua_isnil(L2, -1) && mode == LookupMode::LaneBody) { | ||
590 | lua_pop(L2, 2); // L1: ... t ... L2: | ||
591 | STACK_CHECK(L2, 0); | ||
592 | return false; | ||
593 | } else if (!lua_istable(L2, -1)) { // this can happen if someone decides to replace same already registered item (for a example a standard lib function) with a table | ||
594 | lua_getglobal(L1, "decoda_name"); // L1: ... t ... decoda_name | ||
595 | char const* from{ lua_tostring(L1, -1) }; | ||
596 | lua_pop(L1, 1); // L1: ... t ... | ||
597 | lua_getglobal(L2, "decoda_name"); // L1: ... t ... L2: {} t decoda_name | ||
598 | char const* to{ lua_tostring(L2, -1) }; | ||
599 | lua_pop(L2, 1); // L1: ... t ... L2: {} t | ||
600 | raise_luaL_error( | ||
601 | getErrL(), | ||
602 | "%s: source table '%s' found as %s in %s destination transfer database.", | ||
603 | from ? from : "main", | ||
604 | fqn, | ||
605 | lua_typename(L2, lua_type_as_enum(L2, -1)), | ||
606 | to ? to : "main" | ||
607 | ); | ||
608 | } | ||
609 | lua_remove(L2, -2); // L1: ... t ... L2: t | ||
610 | break; | ||
611 | } | ||
612 | STACK_CHECK(L2, 1); | ||
613 | return true; | ||
614 | } | ||
615 | |||
616 | // ################################################################################################# | ||
617 | |||
618 | // Check if we've already copied the same table from 'L1', and reuse the old copy. This allows table upvalues shared by multiple | ||
619 | // local functions to point to the same table, also in the target. | ||
620 | // Always pushes a table to 'L2'. | ||
621 | // Returns true if the table was cached (no need to fill it!); false if it's a virgin. | ||
622 | [[nodiscard]] bool InterCopyContext::push_cached_table() const | ||
623 | { | ||
624 | void const* p{ lua_topointer(L1, L1_i) }; | ||
625 | |||
626 | LUA_ASSERT(L1, L2_cache_i != 0); | ||
627 | STACK_GROW(L2, 3); | ||
628 | STACK_CHECK_START_REL(L2, 0); | ||
629 | |||
630 | // We don't need to use the from state ('L1') in ID since the life span | ||
631 | // is only for the duration of a copy (both states are locked). | ||
632 | // push a light userdata uniquely representing the table | ||
633 | lua_pushlightuserdata(L2, const_cast<void*>(p)); // L1: ... t ... L2: ... p | ||
634 | |||
635 | // fprintf(stderr, "<< ID: %s >>\n", lua_tostring(L2, -1)); | ||
636 | |||
637 | lua_rawget(L2, L2_cache_i); // L1: ... t ... L2: ... {cached|nil} | ||
638 | bool const not_found_in_cache{ lua_isnil(L2, -1) }; | ||
639 | if (not_found_in_cache) { | ||
640 | // create a new entry in the cache | ||
641 | lua_pop(L2, 1); // L1: ... t ... L2: ... | ||
642 | lua_newtable(L2); // L1: ... t ... L2: ... {} | ||
643 | lua_pushlightuserdata(L2, const_cast<void*>(p)); // L1: ... t ... L2: ... {} p | ||
644 | lua_pushvalue(L2, -2); // L1: ... t ... L2: ... {} p {} | ||
645 | lua_rawset(L2, L2_cache_i); // L1: ... t ... L2: ... {} | ||
646 | } | ||
647 | STACK_CHECK(L2, 1); | ||
648 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
649 | return !not_found_in_cache; | ||
650 | } | ||
651 | |||
652 | // ################################################################################################# | ||
653 | |||
654 | // Return some name helping to identify an object | 351 | // Return some name helping to identify an object |
655 | [[nodiscard]] static int DiscoverObjectNameRecur(lua_State* L_, int shortest_, int depth_) | 352 | [[nodiscard]] static int DiscoverObjectNameRecur(lua_State* L_, int shortest_, int depth_) |
656 | { | 353 | { |
@@ -826,1001 +523,3 @@ int luaG_nameof(lua_State* L_) | |||
826 | lua_replace(L_, -3); // L_: "type" "result" | 523 | lua_replace(L_, -3); // L_: "type" "result" |
827 | return 2; | 524 | return 2; |
828 | } | 525 | } |
829 | |||
830 | // ################################################################################################# | ||
831 | |||
832 | // Push a looked-up native/LuaJIT function. | ||
833 | void InterCopyContext::lookup_native_func() const | ||
834 | { | ||
835 | // get the name of the function we want to send | ||
836 | size_t len; | ||
837 | char const* const fqn{ find_lookup_name(L1, L1_i, mode, name, &len) }; | ||
838 | // push the equivalent function in the destination's stack, retrieved from the lookup table | ||
839 | STACK_CHECK_START_REL(L2, 0); | ||
840 | STACK_GROW(L2, 3); // up to 3 slots are necessary on error | ||
841 | switch (mode) { | ||
842 | default: // shouldn't happen, in theory... | ||
843 | raise_luaL_error(getErrL(), "internal error: unknown lookup mode"); | ||
844 | break; | ||
845 | |||
846 | case LookupMode::ToKeeper: | ||
847 | // push a sentinel closure that holds the lookup name as upvalue | ||
848 | lua_pushlstring(L2, fqn, len); // L1: ... f ... L2: "f.q.n" | ||
849 | lua_pushcclosure(L2, func_lookup_sentinel, 1); // L1: ... f ... L2: f | ||
850 | break; | ||
851 | |||
852 | case LookupMode::LaneBody: | ||
853 | case LookupMode::FromKeeper: | ||
854 | kLookupRegKey.pushValue(L2); // L1: ... f ... L2: {} | ||
855 | STACK_CHECK(L2, 1); | ||
856 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
857 | lua_pushlstring(L2, fqn, len); // L1: ... f ... L2: {} "f.q.n" | ||
858 | lua_rawget(L2, -2); // L1: ... f ... L2: {} f | ||
859 | // nil means we don't know how to transfer stuff: user should do something | ||
860 | // anything other than function or table should not happen! | ||
861 | if (!lua_isfunction(L2, -1) && !lua_istable(L2, -1)) { | ||
862 | lua_getglobal(L1, "decoda_name"); // L1: ... f ... decoda_name | ||
863 | char const* const from{ lua_tostring(L1, -1) }; | ||
864 | lua_pop(L1, 1); // L1: ... f ... | ||
865 | lua_getglobal(L2, "decoda_name"); // L1: ... f ... L2: {} f decoda_name | ||
866 | char const* const to{ lua_tostring(L2, -1) }; | ||
867 | lua_pop(L2, 1); // L2: {} f | ||
868 | // when mode_ == LookupMode::FromKeeper, L is a keeper state and L2 is not, therefore L2 is the state where we want to raise the error | ||
869 | raise_luaL_error( | ||
870 | getErrL() | ||
871 | , "%s%s: function '%s' not found in %s destination transfer database." | ||
872 | , lua_isnil(L2, -1) ? "" : "INTERNAL ERROR IN " | ||
873 | , from ? from : "main" | ||
874 | , fqn | ||
875 | , to ? to : "main" | ||
876 | ); | ||
877 | return; | ||
878 | } | ||
879 | lua_remove(L2, -2); // L2: f | ||
880 | break; | ||
881 | |||
882 | /* keep it in case I need it someday, who knows... | ||
883 | case LookupMode::RawFunctions: | ||
884 | { | ||
885 | int n; | ||
886 | char const* upname; | ||
887 | lua_CFunction f = lua_tocfunction( L, i); | ||
888 | // copy upvalues | ||
889 | for (n = 0; (upname = lua_getupvalue( L, i, 1 + n)) != nullptr; ++ n) { | ||
890 | luaG_inter_move( U, L, L2, 1, mode_); // L2: [up[,up ...]] | ||
891 | } | ||
892 | lua_pushcclosure( L2, f, n); // L2: | ||
893 | } | ||
894 | break; | ||
895 | */ | ||
896 | } | ||
897 | STACK_CHECK(L2, 1); | ||
898 | } | ||
899 | |||
900 | // ################################################################################################# | ||
901 | |||
902 | #if USE_DEBUG_SPEW() | ||
903 | static char const* lua_type_names[] = { | ||
904 | "LUA_TNIL" | ||
905 | , "LUA_TBOOLEAN" | ||
906 | , "LUA_TLIGHTUSERDATA" | ||
907 | , "LUA_TNUMBER" | ||
908 | , "LUA_TSTRING" | ||
909 | , "LUA_TTABLE" | ||
910 | , "LUA_TFUNCTION" | ||
911 | , "LUA_TUSERDATA" | ||
912 | , "LUA_TTHREAD" | ||
913 | , "<LUA_NUMTAGS>" // not really a type | ||
914 | , "LUA_TJITCDATA" // LuaJIT specific | ||
915 | }; | ||
916 | static char const* vt_names[] = { | ||
917 | "VT::NORMAL" | ||
918 | , "VT::KEY" | ||
919 | , "VT::METATABLE" | ||
920 | }; | ||
921 | #endif // USE_DEBUG_SPEW() | ||
922 | |||
923 | // ################################################################################################# | ||
924 | |||
925 | // Lua 5.4.3 style of dumping (see lstrlib.c) | ||
926 | // we have to do it that way because we can't unbalance the stack between buffer operations | ||
927 | // namely, this means we can't push a function on top of the stack *after* we initialize the buffer! | ||
928 | // luckily, this also works with earlier Lua versions | ||
929 | [[nodiscard]] static int buf_writer(lua_State* L_, void const* b_, size_t size_, void* ud_) | ||
930 | { | ||
931 | luaL_Buffer* const B{ static_cast<luaL_Buffer*>(ud_) }; | ||
932 | if (!B->L) { | ||
933 | luaL_buffinit(L_, B); | ||
934 | } | ||
935 | luaL_addlstring(B, static_cast<char const*>(b_), size_); | ||
936 | return 0; | ||
937 | } | ||
938 | |||
939 | // ################################################################################################# | ||
940 | |||
941 | // Copy a function over, which has not been found in the cache. | ||
942 | // L2 has the cache key for this function at the top of the stack | ||
943 | void InterCopyContext::copy_func() const | ||
944 | { | ||
945 | LUA_ASSERT(L1, L2_cache_i != 0); // L2: ... {cache} ... p | ||
946 | STACK_GROW(L1, 2); | ||
947 | STACK_CHECK_START_REL(L1, 0); | ||
948 | |||
949 | // 'lua_dump()' needs the function at top of stack | ||
950 | // if already on top of the stack, no need to push again | ||
951 | bool const needToPush{ L1_i != lua_gettop(L1) }; | ||
952 | if (needToPush) { | ||
953 | lua_pushvalue(L1, L1_i); // L1: ... f | ||
954 | } | ||
955 | |||
956 | // | ||
957 | // "value returned is the error code returned by the last call | ||
958 | // to the writer" (and we only return 0) | ||
959 | // not sure this could ever fail but for memory shortage reasons | ||
960 | // last parameter is Lua 5.4-specific (no stripping) | ||
961 | luaL_Buffer B; | ||
962 | B.L = nullptr; | ||
963 | if (lua504_dump(L1, buf_writer, &B, 0) != 0) { | ||
964 | raise_luaL_error(getErrL(), "internal error: function dump failed."); | ||
965 | } | ||
966 | |||
967 | // pushes dumped string on 'L1' | ||
968 | luaL_pushresult(&B); // L1: ... f b | ||
969 | |||
970 | // if not pushed, no need to pop | ||
971 | if (needToPush) { | ||
972 | lua_remove(L1, -2); // L1: ... b | ||
973 | } | ||
974 | |||
975 | // transfer the bytecode, then the upvalues, to create a similar closure | ||
976 | { | ||
977 | char const* fname = nullptr; | ||
978 | #define LOG_FUNC_INFO 0 | ||
979 | #if LOG_FUNC_INFO | ||
980 | // "To get information about a function you push it onto the | ||
981 | // stack and start the what string with the character '>'." | ||
982 | // | ||
983 | { | ||
984 | lua_Debug ar; | ||
985 | lua_pushvalue(L1, L1_i); // L1: ... b f | ||
986 | // fills 'fname' 'namewhat' and 'linedefined', pops function | ||
987 | lua_getinfo(L1, ">nS", &ar); // L1: ... b | ||
988 | fname = ar.namewhat; | ||
989 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "FNAME: %s @ %d" INDENT_END(U), ar.short_src, ar.linedefined)); // just gives nullptr | ||
990 | } | ||
991 | #endif // LOG_FUNC_INFO | ||
992 | { | ||
993 | size_t sz; | ||
994 | char const* s = lua_tolstring(L1, -1, &sz); // L1: ... b | ||
995 | LUA_ASSERT(L1, s && sz); | ||
996 | STACK_GROW(L2, 2); | ||
997 | // Note: Line numbers seem to be taken precisely from the | ||
998 | // original function. 'fname' is not used since the chunk | ||
999 | // is precompiled (it seems...). | ||
1000 | // | ||
1001 | // TBD: Can we get the function's original name through, as well? | ||
1002 | // | ||
1003 | if (luaL_loadbuffer(L2, s, sz, fname) != 0) { // L2: ... {cache} ... p function | ||
1004 | // chunk is precompiled so only LUA_ERRMEM can happen | ||
1005 | // "Otherwise, it pushes an error message" | ||
1006 | // | ||
1007 | STACK_GROW(L1, 1); | ||
1008 | raise_luaL_error(getErrL(), "%s: %s", fname, lua_tostring(L2, -1)); | ||
1009 | } | ||
1010 | // remove the dumped string | ||
1011 | lua_pop(L1, 1); // ... | ||
1012 | // now set the cache as soon as we can. | ||
1013 | // this is necessary if one of the function's upvalues references it indirectly | ||
1014 | // we need to find it in the cache even if it isn't fully transfered yet | ||
1015 | lua_insert(L2, -2); // L2: ... {cache} ... function p | ||
1016 | lua_pushvalue(L2, -2); // L2: ... {cache} ... function p function | ||
1017 | // cache[p] = function | ||
1018 | lua_rawset(L2, L2_cache_i); // L2: ... {cache} ... function | ||
1019 | } | ||
1020 | STACK_CHECK(L1, 0); | ||
1021 | |||
1022 | /* push over any upvalues; references to this function will come from | ||
1023 | * cache so we don't end up in eternal loop. | ||
1024 | * Lua5.2 and Lua5.3: one of the upvalues is _ENV, which we don't want to copy! | ||
1025 | * instead, the function shall have LUA_RIDX_GLOBALS taken in the destination state! | ||
1026 | */ | ||
1027 | int n{ 0 }; | ||
1028 | { | ||
1029 | InterCopyContext c{ U, L2, L1, L2_cache_i, {}, VT::NORMAL, mode, {} }; | ||
1030 | #if LUA_VERSION_NUM >= 502 | ||
1031 | // Starting with Lua 5.2, each Lua function gets its environment as one of its upvalues (named LUA_ENV, aka "_ENV" by default) | ||
1032 | // Generally this is LUA_RIDX_GLOBALS, which we don't want to copy from the source to the destination state... | ||
1033 | // -> if we encounter an upvalue equal to the global table in the source, bind it to the destination's global table | ||
1034 | lua_pushglobaltable(L1); // L1: ... _G | ||
1035 | #endif // LUA_VERSION_NUM | ||
1036 | for (n = 0; (c.name = lua_getupvalue(L1, L1_i, 1 + n)) != nullptr; ++n) { // L1: ... _G up[n] | ||
1037 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "UPNAME[%d]: %s -> " INDENT_END(U), n, c.name)); | ||
1038 | #if LUA_VERSION_NUM >= 502 | ||
1039 | if (lua_rawequal(L1, -1, -2)) { // is the upvalue equal to the global table? | ||
1040 | DEBUGSPEW_CODE(fprintf(stderr, "pushing destination global scope\n")); | ||
1041 | lua_pushglobaltable(L2); // L2: ... {cache} ... function <upvalues> | ||
1042 | } else | ||
1043 | #endif // LUA_VERSION_NUM | ||
1044 | { | ||
1045 | DEBUGSPEW_CODE(fprintf(stderr, "copying value\n")); | ||
1046 | c.L1_i = SourceIndex{ lua_gettop(L1) }; | ||
1047 | if (!c.inter_copy_one()) { // L2: ... {cache} ... function <upvalues> | ||
1048 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
1049 | } | ||
1050 | } | ||
1051 | lua_pop(L1, 1); // L1: ... _G | ||
1052 | } | ||
1053 | #if LUA_VERSION_NUM >= 502 | ||
1054 | lua_pop(L1, 1); // L1: ... | ||
1055 | #endif // LUA_VERSION_NUM | ||
1056 | } | ||
1057 | // L2: ... {cache} ... function + 'n' upvalues (>=0) | ||
1058 | |||
1059 | STACK_CHECK(L1, 0); | ||
1060 | |||
1061 | // Set upvalues (originally set to 'nil' by 'lua_load') | ||
1062 | for (int const func_index{ lua_gettop(L2) - n }; n > 0; --n) { | ||
1063 | char const* rc{ lua_setupvalue(L2, func_index, n) }; // L2: ... {cache} ... function | ||
1064 | // | ||
1065 | // "assigns the value at the top of the stack to the upvalue and returns its name. | ||
1066 | // It also pops the value from the stack." | ||
1067 | |||
1068 | LUA_ASSERT(L1, rc); // not having enough slots? | ||
1069 | } | ||
1070 | // once all upvalues have been set we are left | ||
1071 | // with the function at the top of the stack // L2: ... {cache} ... function | ||
1072 | } | ||
1073 | STACK_CHECK(L1, 0); | ||
1074 | } | ||
1075 | |||
1076 | // ################################################################################################# | ||
1077 | |||
1078 | // Check if we've already copied the same function from 'L1', and reuse the old copy. | ||
1079 | // Always pushes a function to 'L2'. | ||
1080 | void InterCopyContext::copy_cached_func() const | ||
1081 | { | ||
1082 | FuncSubType const funcSubType{ luaG_getfuncsubtype(L1, L1_i) }; | ||
1083 | if (funcSubType == FuncSubType::Bytecode) { | ||
1084 | void* const aspointer = const_cast<void*>(lua_topointer(L1, L1_i)); | ||
1085 | // TBD: Merge this and same code for tables | ||
1086 | LUA_ASSERT(L1, L2_cache_i != 0); | ||
1087 | |||
1088 | STACK_GROW(L2, 2); | ||
1089 | |||
1090 | // L2_cache[id_str]= function | ||
1091 | // | ||
1092 | STACK_CHECK_START_REL(L2, 0); | ||
1093 | |||
1094 | // We don't need to use the from state ('L1') in ID since the life span | ||
1095 | // is only for the duration of a copy (both states are locked). | ||
1096 | |||
1097 | // push a light userdata uniquely representing the function | ||
1098 | lua_pushlightuserdata(L2, aspointer); // L2: ... {cache} ... p | ||
1099 | |||
1100 | // fprintf( stderr, "<< ID: %s >>\n", lua_tostring( L2, -1)); | ||
1101 | |||
1102 | lua_pushvalue(L2, -1); // L2: ... {cache} ... p p | ||
1103 | lua_rawget(L2, L2_cache_i); // L2: ... {cache} ... p function|nil|true | ||
1104 | |||
1105 | if (lua_isnil(L2, -1)) { // function is unknown | ||
1106 | lua_pop(L2, 1); // L2: ... {cache} ... p | ||
1107 | |||
1108 | // Set to 'true' for the duration of creation; need to find self-references | ||
1109 | // via upvalues | ||
1110 | // | ||
1111 | // pushes a copy of the func, stores a reference in the cache | ||
1112 | copy_func(); // L2: ... {cache} ... function | ||
1113 | } else { // found function in the cache | ||
1114 | lua_remove(L2, -2); // L2: ... {cache} ... function | ||
1115 | } | ||
1116 | STACK_CHECK(L2, 1); | ||
1117 | LUA_ASSERT(L1, lua_isfunction(L2, -1)); | ||
1118 | } else { // function is native/LuaJIT: no need to cache | ||
1119 | lookup_native_func(); // L2: ... {cache} ... function | ||
1120 | // if the function was in fact a lookup sentinel, we can either get a function or a table here | ||
1121 | LUA_ASSERT(L1, lua_isfunction(L2, -1) || lua_istable(L2, -1)); | ||
1122 | } | ||
1123 | } | ||
1124 | |||
1125 | // ################################################################################################# | ||
1126 | |||
1127 | [[nodiscard]] bool InterCopyContext::push_cached_metatable() const | ||
1128 | { | ||
1129 | STACK_CHECK_START_REL(L1, 0); | ||
1130 | if (!lua_getmetatable(L1, L1_i)) { // L1: ... mt | ||
1131 | STACK_CHECK(L1, 0); | ||
1132 | return false; | ||
1133 | } | ||
1134 | STACK_CHECK(L1, 1); | ||
1135 | |||
1136 | lua_Integer const mt_id{ get_mt_id(U, L1, -1) }; // Unique id for the metatable | ||
1137 | |||
1138 | STACK_CHECK_START_REL(L2, 0); | ||
1139 | STACK_GROW(L2, 4); | ||
1140 | // do we already know this metatable? | ||
1141 | std::ignore = kMtIdRegKey.getSubTable(L2, 0, 0); // L2: _R[kMtIdRegKey] | ||
1142 | lua_pushinteger(L2, mt_id); // L2: _R[kMtIdRegKey] id | ||
1143 | lua_rawget(L2, -2); // L2: _R[kMtIdRegKey] mt|nil | ||
1144 | STACK_CHECK(L2, 2); | ||
1145 | |||
1146 | if (lua_isnil(L2, -1)) { // L2 did not know the metatable | ||
1147 | lua_pop(L2, 1); // L2: _R[kMtIdRegKey] | ||
1148 | InterCopyContext const c{ U, L2, L1, L2_cache_i, SourceIndex{ lua_gettop(L1) }, VT::METATABLE, mode, name }; | ||
1149 | if (!c.inter_copy_one()) { // L2: _R[kMtIdRegKey] mt? | ||
1150 | raise_luaL_error(getErrL(), "Error copying a metatable"); | ||
1151 | } | ||
1152 | |||
1153 | STACK_CHECK(L2, 2); // L2: _R[kMtIdRegKey] mt | ||
1154 | // mt_id -> metatable | ||
1155 | lua_pushinteger(L2, mt_id); // L2: _R[kMtIdRegKey] mt id | ||
1156 | lua_pushvalue(L2, -2); // L2: _R[kMtIdRegKey] mt id mt | ||
1157 | lua_rawset(L2, -4); // L2: _R[kMtIdRegKey] mt | ||
1158 | |||
1159 | // metatable -> mt_id | ||
1160 | lua_pushvalue(L2, -1); // L2: _R[kMtIdRegKey] mt mt | ||
1161 | lua_pushinteger(L2, mt_id); // L2: _R[kMtIdRegKey] mt mt id | ||
1162 | lua_rawset(L2, -4); // L2: _R[kMtIdRegKey] mt | ||
1163 | STACK_CHECK(L2, 2); | ||
1164 | } | ||
1165 | lua_remove(L2, -2); // L2: mt | ||
1166 | |||
1167 | lua_pop(L1, 1); // L1: ... | ||
1168 | STACK_CHECK(L2, 1); | ||
1169 | STACK_CHECK(L1, 0); | ||
1170 | return true; | ||
1171 | } | ||
1172 | |||
1173 | // ################################################################################################# | ||
1174 | |||
1175 | void InterCopyContext::inter_copy_keyvaluepair() const | ||
1176 | { | ||
1177 | SourceIndex const val_i{ lua_gettop(L1) }; | ||
1178 | SourceIndex const key_i{ val_i - 1 }; | ||
1179 | |||
1180 | // For the key, only basic key types are copied over. others ignored | ||
1181 | InterCopyContext c{ U, L2, L1, L2_cache_i, key_i, VT::KEY, mode, name }; | ||
1182 | if (!c.inter_copy_one()) { | ||
1183 | return; | ||
1184 | // we could raise an error instead of ignoring the table entry, like so: | ||
1185 | // raise_luaL_error(L1, "Unable to copy %s key '%s' because of value is of type '%s'", (vt == VT::NORMAL) ? "table" : "metatable", name, luaL_typename(L1, key_i)); | ||
1186 | // maybe offer this possibility as a global configuration option, or a linda setting, or as a parameter of the call causing the transfer? | ||
1187 | } | ||
1188 | |||
1189 | char* valPath{ nullptr }; | ||
1190 | if (U->verboseErrors) { | ||
1191 | // for debug purposes, let's try to build a useful name | ||
1192 | if (lua_type(L1, key_i) == LUA_TSTRING) { | ||
1193 | char const* key{ lua_tostring(L1, key_i) }; | ||
1194 | size_t const keyRawLen = lua_rawlen(L1, key_i); | ||
1195 | size_t const bufLen = strlen(name) + keyRawLen + 2; | ||
1196 | valPath = (char*) alloca(bufLen); | ||
1197 | sprintf(valPath, "%s.%*s", name, (int) keyRawLen, key); | ||
1198 | key = nullptr; | ||
1199 | } | ||
1200 | #if defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
1201 | else if (lua_isinteger(L1, key_i)) { | ||
1202 | lua_Integer const key{ lua_tointeger(L1, key_i) }; | ||
1203 | valPath = (char*) alloca(strlen(name) + 32 + 3); | ||
1204 | sprintf(valPath, "%s[" LUA_INTEGER_FMT "]", name, key); | ||
1205 | } | ||
1206 | #endif // defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
1207 | else if (lua_type(L1, key_i) == LUA_TNUMBER) { | ||
1208 | lua_Number const key{ lua_tonumber(L1, key_i) }; | ||
1209 | valPath = (char*) alloca(strlen(name) + 32 + 3); | ||
1210 | sprintf(valPath, "%s[" LUA_NUMBER_FMT "]", name, key); | ||
1211 | } else if (lua_type(L1, key_i) == LUA_TLIGHTUSERDATA) { | ||
1212 | void* const key{ lua_touserdata(L1, key_i) }; | ||
1213 | valPath = (char*) alloca(strlen(name) + 16 + 5); | ||
1214 | sprintf(valPath, "%s[U:%p]", name, key); | ||
1215 | } else if (lua_type(L1, key_i) == LUA_TBOOLEAN) { | ||
1216 | int const key{ lua_toboolean(L1, key_i) }; | ||
1217 | valPath = (char*) alloca(strlen(name) + 8); | ||
1218 | sprintf(valPath, "%s[%s]", name, key ? "true" : "false"); | ||
1219 | } | ||
1220 | } | ||
1221 | c.L1_i = SourceIndex{ val_i }; | ||
1222 | // Contents of metatables are copied with cache checking. important to detect loops. | ||
1223 | c.vt = VT::NORMAL; | ||
1224 | c.name = valPath ? valPath : name; | ||
1225 | if (c.inter_copy_one()) { | ||
1226 | LUA_ASSERT(L1, lua_istable(L2, -3)); | ||
1227 | lua_rawset(L2, -3); // add to table (pops key & val) | ||
1228 | } else { | ||
1229 | raise_luaL_error(getErrL(), "Unable to copy %s entry '%s' because of value is of type '%s'", (vt == VT::NORMAL) ? "table" : "metatable", valPath, luaL_typename(L1, val_i)); | ||
1230 | } | ||
1231 | } | ||
1232 | |||
1233 | // ################################################################################################# | ||
1234 | |||
1235 | [[nodiscard]] bool InterCopyContext::tryCopyClonable() const | ||
1236 | { | ||
1237 | SourceIndex const L1i{ lua_absindex(L1, L1_i) }; | ||
1238 | void* const source{ lua_touserdata(L1, L1i) }; | ||
1239 | |||
1240 | STACK_CHECK_START_REL(L1, 0); | ||
1241 | STACK_CHECK_START_REL(L2, 0); | ||
1242 | |||
1243 | // Check if the source was already cloned during this copy | ||
1244 | lua_pushlightuserdata(L2, source); // L2: ... source | ||
1245 | lua_rawget(L2, L2_cache_i); // L2: ... clone? | ||
1246 | if (!lua_isnil(L2, -1)) { | ||
1247 | STACK_CHECK(L2, 1); | ||
1248 | return true; | ||
1249 | } else { | ||
1250 | lua_pop(L2, 1); // L2: ... | ||
1251 | } | ||
1252 | STACK_CHECK(L2, 0); | ||
1253 | |||
1254 | // no metatable? -> not clonable | ||
1255 | if (!lua_getmetatable(L1, L1i)) { // L1: ... mt? | ||
1256 | STACK_CHECK(L1, 0); | ||
1257 | return false; | ||
1258 | } | ||
1259 | |||
1260 | // no __lanesclone? -> not clonable | ||
1261 | lua_getfield(L1, -1, "__lanesclone"); // L1: ... mt __lanesclone? | ||
1262 | if (lua_isnil(L1, -1)) { | ||
1263 | lua_pop(L1, 2); // L1: ... | ||
1264 | STACK_CHECK(L1, 0); | ||
1265 | return false; | ||
1266 | } | ||
1267 | |||
1268 | // we need to copy over the uservalues of the userdata as well | ||
1269 | { | ||
1270 | int const mt{ lua_absindex(L1, -2) }; // L1: ... mt __lanesclone | ||
1271 | size_t const userdata_size{ lua_rawlen(L1, L1i) }; | ||
1272 | // extract all the uservalues, but don't transfer them yet | ||
1273 | int uvi = 0; | ||
1274 | while (lua_getiuservalue(L1, L1i, ++uvi) != LUA_TNONE) {} // L1: ... mt __lanesclone [uv]+ nil | ||
1275 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now | ||
1276 | lua_pop(L1, 1); // L1: ... mt __lanesclone [uv]+ | ||
1277 | --uvi; | ||
1278 | // create the clone userdata with the required number of uservalue slots | ||
1279 | void* const clone{ lua_newuserdatauv(L2, userdata_size, uvi) }; // L2: ... u | ||
1280 | // copy the metatable in the target state, and give it to the clone we put there | ||
1281 | InterCopyContext c{ U, L2, L1, L2_cache_i, SourceIndex{ mt }, VT::NORMAL, mode, name }; | ||
1282 | if (c.inter_copy_one()) { // L2: ... u mt|sentinel | ||
1283 | if (LookupMode::ToKeeper == mode) { // L2: ... u sentinel | ||
1284 | LUA_ASSERT(L1, lua_tocfunction(L2, -1) == table_lookup_sentinel); | ||
1285 | // we want to create a new closure with a 'clone sentinel' function, where the upvalues are the userdata and the metatable fqn | ||
1286 | lua_getupvalue(L2, -1, 1); // L2: ... u sentinel fqn | ||
1287 | lua_remove(L2, -2); // L2: ... u fqn | ||
1288 | lua_insert(L2, -2); // L2: ... fqn u | ||
1289 | lua_pushcclosure(L2, userdata_clone_sentinel, 2); // L2: ... userdata_clone_sentinel | ||
1290 | } else { // from keeper or direct // L2: ... u mt | ||
1291 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
1292 | lua_setmetatable(L2, -2); // L2: ... u | ||
1293 | } | ||
1294 | STACK_CHECK(L2, 1); | ||
1295 | } else { | ||
1296 | raise_luaL_error(getErrL(), "Error copying a metatable"); | ||
1297 | } | ||
1298 | // first, add the entry in the cache (at this point it is either the actual userdata or the keeper sentinel | ||
1299 | lua_pushlightuserdata(L2, source); // L2: ... u source | ||
1300 | lua_pushvalue(L2, -2); // L2: ... u source u | ||
1301 | lua_rawset(L2, L2_cache_i); // L2: ... u | ||
1302 | // make sure we have the userdata now | ||
1303 | if (LookupMode::ToKeeper == mode) { // L2: ... userdata_clone_sentinel | ||
1304 | lua_getupvalue(L2, -1, 2); // L2: ... userdata_clone_sentinel u | ||
1305 | } | ||
1306 | // assign uservalues | ||
1307 | while (uvi > 0) { | ||
1308 | c.L1_i = SourceIndex{ lua_absindex(L1, -1) }; | ||
1309 | if (!c.inter_copy_one()) { // L2: ... u uv | ||
1310 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
1311 | } | ||
1312 | lua_pop(L1, 1); // L1: ... mt __lanesclone [uv]* | ||
1313 | // this pops the value from the stack | ||
1314 | lua_setiuservalue(L2, -2, uvi); // L2: ... u | ||
1315 | --uvi; | ||
1316 | } | ||
1317 | // when we are done, all uservalues are popped from the source stack, and we want only the single transferred value in the destination | ||
1318 | if (LookupMode::ToKeeper == mode) { // L2: ... userdata_clone_sentinel u | ||
1319 | lua_pop(L2, 1); // L2: ... userdata_clone_sentinel | ||
1320 | } | ||
1321 | STACK_CHECK(L2, 1); | ||
1322 | STACK_CHECK(L1, 2); | ||
1323 | // call cloning function in source state to perform the actual memory cloning | ||
1324 | lua_pushlightuserdata(L1, clone); // L1: ... mt __lanesclone clone | ||
1325 | lua_pushlightuserdata(L1, source); // L1: ... mt __lanesclone clone source | ||
1326 | lua_pushinteger(L1, static_cast<lua_Integer>(userdata_size)); // L1: ... mt __lanesclone clone source size | ||
1327 | lua_call(L1, 3, 0); // L1: ... mt | ||
1328 | STACK_CHECK(L1, 1); | ||
1329 | } | ||
1330 | |||
1331 | STACK_CHECK(L2, 1); | ||
1332 | lua_pop(L1, 1); // L1: ... | ||
1333 | STACK_CHECK(L1, 0); | ||
1334 | return true; | ||
1335 | } | ||
1336 | |||
1337 | // ################################################################################################# | ||
1338 | |||
1339 | [[nodiscard]] bool InterCopyContext::inter_copy_userdata() const | ||
1340 | { | ||
1341 | STACK_CHECK_START_REL(L1, 0); | ||
1342 | STACK_CHECK_START_REL(L2, 0); | ||
1343 | if (vt == VT::KEY) { | ||
1344 | return false; | ||
1345 | } | ||
1346 | |||
1347 | // try clonable userdata first | ||
1348 | if (tryCopyClonable()) { | ||
1349 | STACK_CHECK(L1, 0); | ||
1350 | STACK_CHECK(L2, 1); | ||
1351 | return true; | ||
1352 | } | ||
1353 | |||
1354 | STACK_CHECK(L1, 0); | ||
1355 | STACK_CHECK(L2, 0); | ||
1356 | |||
1357 | // Allow only deep userdata entities to be copied across | ||
1358 | DEBUGSPEW_CODE(fprintf(stderr, "USERDATA\n")); | ||
1359 | if (tryCopyDeep()) { | ||
1360 | STACK_CHECK(L1, 0); | ||
1361 | STACK_CHECK(L2, 1); | ||
1362 | return true; | ||
1363 | } | ||
1364 | |||
1365 | STACK_CHECK(L1, 0); | ||
1366 | STACK_CHECK(L2, 0); | ||
1367 | |||
1368 | // Not a deep or clonable full userdata | ||
1369 | if (U->demoteFullUserdata) { // attempt demotion to light userdata | ||
1370 | void* const lud{ lua_touserdata(L1, L1_i) }; | ||
1371 | lua_pushlightuserdata(L2, lud); | ||
1372 | } else { // raise an error | ||
1373 | raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes"); | ||
1374 | } | ||
1375 | |||
1376 | STACK_CHECK(L2, 1); | ||
1377 | STACK_CHECK(L1, 0); | ||
1378 | return true; | ||
1379 | } | ||
1380 | |||
1381 | // ################################################################################################# | ||
1382 | |||
1383 | [[nodiscard]] bool InterCopyContext::inter_copy_function() const | ||
1384 | { | ||
1385 | if (vt == VT::KEY) { | ||
1386 | return false; | ||
1387 | } | ||
1388 | |||
1389 | STACK_CHECK_START_REL(L1, 0); | ||
1390 | STACK_CHECK_START_REL(L2, 0); | ||
1391 | DEBUGSPEW_CODE(fprintf(stderr, "FUNCTION %s\n", name)); | ||
1392 | |||
1393 | if (lua_tocfunction(L1, L1_i) == userdata_clone_sentinel) { // we are actually copying a clonable full userdata from a keeper | ||
1394 | // clone the full userdata again | ||
1395 | |||
1396 | // let's see if we already restored this userdata | ||
1397 | lua_getupvalue(L1, L1_i, 2); // L1: ... u | ||
1398 | void* source = lua_touserdata(L1, -1); | ||
1399 | lua_pushlightuserdata(L2, source); // L2: ... source | ||
1400 | lua_rawget(L2, L2_cache_i); // L2: ... u? | ||
1401 | if (!lua_isnil(L2, -1)) { | ||
1402 | lua_pop(L1, 1); // L1: ... | ||
1403 | STACK_CHECK(L1, 0); | ||
1404 | STACK_CHECK(L2, 1); | ||
1405 | return true; | ||
1406 | } | ||
1407 | lua_pop(L2, 1); // L2: ... | ||
1408 | |||
1409 | // userdata_clone_sentinel has 2 upvalues: the fqn of its metatable, and the userdata itself | ||
1410 | bool const found{ lookup_table() }; // L2: ... mt? | ||
1411 | if (!found) { | ||
1412 | STACK_CHECK(L2, 0); | ||
1413 | return false; | ||
1414 | } | ||
1415 | // 'L1_i' slot was the proxy closure, but from now on we operate onthe actual userdata we extracted from it | ||
1416 | SourceIndex const source_i{ lua_gettop(L1) }; | ||
1417 | source = lua_touserdata(L1, -1); | ||
1418 | void* clone{ nullptr }; | ||
1419 | // get the number of bytes to allocate for the clone | ||
1420 | size_t const userdata_size{ lua_rawlen(L1, -1) }; | ||
1421 | { | ||
1422 | // extract uservalues (don't transfer them yet) | ||
1423 | int uvi = 0; | ||
1424 | while (lua_getiuservalue(L1, source_i, ++uvi) != LUA_TNONE) {} // L1: ... u uv | ||
1425 | // when lua_getiuservalue() returned LUA_TNONE, it pushed a nil. pop it now | ||
1426 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
1427 | --uvi; | ||
1428 | STACK_CHECK(L1, uvi + 1); | ||
1429 | // create the clone userdata with the required number of uservalue slots | ||
1430 | clone = lua_newuserdatauv(L2, userdata_size, uvi); // L2: ... mt u | ||
1431 | // add it in the cache | ||
1432 | lua_pushlightuserdata(L2, source); // L2: ... mt u source | ||
1433 | lua_pushvalue(L2, -2); // L2: ... mt u source u | ||
1434 | lua_rawset(L2, L2_cache_i); // L2: ... mt u | ||
1435 | // set metatable | ||
1436 | lua_pushvalue(L2, -2); // L2: ... mt u mt | ||
1437 | lua_setmetatable(L2, -2); // L2: ... mt u | ||
1438 | // transfer and assign uservalues | ||
1439 | InterCopyContext c{ *this }; | ||
1440 | while (uvi > 0) { | ||
1441 | c.L1_i = SourceIndex{ lua_absindex(L1, -1) }; | ||
1442 | if (!c.inter_copy_one()) { // L2: ... mt u uv | ||
1443 | raise_luaL_error(getErrL(), "Cannot copy upvalue type '%s'", luaL_typename(L1, -1)); | ||
1444 | } | ||
1445 | lua_pop(L1, 1); // L1: ... u [uv]* | ||
1446 | // this pops the value from the stack | ||
1447 | lua_setiuservalue(L2, -2, uvi); // L2: ... mt u | ||
1448 | --uvi; | ||
1449 | } | ||
1450 | // when we are done, all uservalues are popped from the stack, we can pop the source as well | ||
1451 | lua_pop(L1, 1); // L1: ... | ||
1452 | STACK_CHECK(L1, 0); | ||
1453 | STACK_CHECK(L2, 2); // L2: ... mt u | ||
1454 | } | ||
1455 | // perform the custom cloning part | ||
1456 | lua_insert(L2, -2); // L2: ... u mt | ||
1457 | // __lanesclone should always exist because we wouldn't be restoring data from a userdata_clone_sentinel closure to begin with | ||
1458 | lua_getfield(L2, -1, "__lanesclone"); // L2: ... u mt __lanesclone | ||
1459 | lua_remove(L2, -2); // L2: ... u __lanesclone | ||
1460 | lua_pushlightuserdata(L2, clone); // L2: ... u __lanesclone clone | ||
1461 | lua_pushlightuserdata(L2, source); // L2: ... u __lanesclone clone source | ||
1462 | lua_pushinteger(L2, userdata_size); // L2: ... u __lanesclone clone source size | ||
1463 | // clone:__lanesclone(dest, source, size) | ||
1464 | lua_call(L2, 3, 0); // L2: ... u | ||
1465 | } else { // regular function | ||
1466 | DEBUGSPEW_CODE(fprintf(stderr, "FUNCTION %s\n", name)); | ||
1467 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
1468 | copy_cached_func(); // L2: ... f | ||
1469 | } | ||
1470 | STACK_CHECK(L2, 1); | ||
1471 | STACK_CHECK(L1, 0); | ||
1472 | return true; | ||
1473 | } | ||
1474 | |||
1475 | // ################################################################################################# | ||
1476 | |||
1477 | [[nodiscard]] bool InterCopyContext::inter_copy_table() const | ||
1478 | { | ||
1479 | if (vt == VT::KEY) { | ||
1480 | return false; | ||
1481 | } | ||
1482 | |||
1483 | STACK_CHECK_START_REL(L1, 0); | ||
1484 | STACK_CHECK_START_REL(L2, 0); | ||
1485 | DEBUGSPEW_CODE(fprintf(stderr, "TABLE %s\n", name)); | ||
1486 | |||
1487 | /* | ||
1488 | * First, let's try to see if this table is special (aka is it some table that we registered in our lookup databases during module registration?) | ||
1489 | * Note that this table CAN be a module table, but we just didn't register it, in which case we'll send it through the table cloning mechanism | ||
1490 | */ | ||
1491 | if (lookup_table()) { | ||
1492 | LUA_ASSERT(L1, lua_istable(L2, -1) || (lua_tocfunction(L2, -1) == table_lookup_sentinel)); // from lookup data. can also be table_lookup_sentinel if this is a table we know | ||
1493 | return true; | ||
1494 | } | ||
1495 | |||
1496 | /* Check if we've already copied the same table from 'L1' (during this transmission), and | ||
1497 | * reuse the old copy. This allows table upvalues shared by multiple | ||
1498 | * local functions to point to the same table, also in the target. | ||
1499 | * Also, this takes care of cyclic tables and multiple references | ||
1500 | * to the same subtable. | ||
1501 | * | ||
1502 | * Note: Even metatables need to go through this test; to detect | ||
1503 | * loops such as those in required module tables (getmetatable(lanes).lanes == lanes) | ||
1504 | */ | ||
1505 | if (push_cached_table()) { | ||
1506 | LUA_ASSERT(L1, lua_istable(L2, -1)); // from cache | ||
1507 | return true; | ||
1508 | } | ||
1509 | LUA_ASSERT(L1, lua_istable(L2, -1)); | ||
1510 | |||
1511 | STACK_GROW(L1, 2); | ||
1512 | STACK_GROW(L2, 2); | ||
1513 | |||
1514 | lua_pushnil(L1); // start iteration | ||
1515 | while (lua_next(L1, L1_i)) { | ||
1516 | // need a function to prevent overflowing the stack with verboseErrors-induced alloca() | ||
1517 | inter_copy_keyvaluepair(); | ||
1518 | lua_pop(L1, 1); // pop value (next round) | ||
1519 | } | ||
1520 | STACK_CHECK(L1, 0); | ||
1521 | STACK_CHECK(L2, 1); | ||
1522 | |||
1523 | // Metatables are expected to be immutable, and copied only once. | ||
1524 | if (push_cached_metatable()) { // L2: ... t mt? | ||
1525 | lua_setmetatable(L2, -2); // L2: ... t | ||
1526 | } | ||
1527 | STACK_CHECK(L2, 1); | ||
1528 | STACK_CHECK(L1, 0); | ||
1529 | return true; | ||
1530 | } | ||
1531 | |||
1532 | // ################################################################################################# | ||
1533 | |||
1534 | [[nodiscard]] bool InterCopyContext::inter_copy_boolean() const | ||
1535 | { | ||
1536 | int const v{ lua_toboolean(L1, L1_i) }; | ||
1537 | DEBUGSPEW_CODE(fprintf(stderr, "%s\n", v ? "true" : "false")); | ||
1538 | lua_pushboolean(L2, v); | ||
1539 | return true; | ||
1540 | } | ||
1541 | |||
1542 | // ################################################################################################# | ||
1543 | |||
1544 | [[nodiscard]] bool InterCopyContext::inter_copy_lightuserdata() const | ||
1545 | { | ||
1546 | void* const p{ lua_touserdata(L1, L1_i) }; | ||
1547 | DEBUGSPEW_CODE(fprintf(stderr, "%p\n", p)); | ||
1548 | lua_pushlightuserdata(L2, p); | ||
1549 | return true; | ||
1550 | } | ||
1551 | |||
1552 | // ################################################################################################# | ||
1553 | |||
1554 | [[nodiscard]] bool InterCopyContext::inter_copy_nil() const | ||
1555 | { | ||
1556 | if (vt == VT::KEY) { | ||
1557 | return false; | ||
1558 | } | ||
1559 | lua_pushnil(L2); | ||
1560 | return true; | ||
1561 | } | ||
1562 | |||
1563 | // ################################################################################################# | ||
1564 | |||
1565 | [[nodiscard]] bool InterCopyContext::inter_copy_number() const | ||
1566 | { | ||
1567 | // LNUM patch support (keeping integer accuracy) | ||
1568 | #if defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
1569 | if (lua_isinteger(L1, L1_i)) { | ||
1570 | lua_Integer const v{ lua_tointeger(L1, L1_i) }; | ||
1571 | DEBUGSPEW_CODE(fprintf(stderr, LUA_INTEGER_FMT "\n", v)); | ||
1572 | lua_pushinteger(L2, v); | ||
1573 | } else | ||
1574 | #endif // defined LUA_LNUM || LUA_VERSION_NUM >= 503 | ||
1575 | { | ||
1576 | lua_Number const v{ lua_tonumber(L1, L1_i) }; | ||
1577 | DEBUGSPEW_CODE(fprintf(stderr, LUA_NUMBER_FMT "\n", v)); | ||
1578 | lua_pushnumber(L2, v); | ||
1579 | } | ||
1580 | return true; | ||
1581 | } | ||
1582 | |||
1583 | // ################################################################################################# | ||
1584 | |||
1585 | [[nodiscard]] bool InterCopyContext::inter_copy_string() const | ||
1586 | { | ||
1587 | size_t len; | ||
1588 | char const* const s{ lua_tolstring(L1, L1_i, &len) }; | ||
1589 | DEBUGSPEW_CODE(fprintf(stderr, "'%s'\n", s)); | ||
1590 | lua_pushlstring(L2, s, len); | ||
1591 | return true; | ||
1592 | } | ||
1593 | |||
1594 | // ################################################################################################# | ||
1595 | |||
1596 | /* | ||
1597 | * Copies a value from 'L1' state (at index 'i') to 'L2' state. Does not remove | ||
1598 | * the original value. | ||
1599 | * | ||
1600 | * NOTE: Both the states must be solely in the current OS thread's possession. | ||
1601 | * | ||
1602 | * 'i' is an absolute index (no -1, ...) | ||
1603 | * | ||
1604 | * Returns true if value was pushed, false if its type is non-supported. | ||
1605 | */ | ||
1606 | [[nodiscard]] bool InterCopyContext::inter_copy_one() const | ||
1607 | { | ||
1608 | static constexpr int kPODmask = (1 << LUA_TNIL) | (1 << LUA_TBOOLEAN) | (1 << LUA_TLIGHTUSERDATA) | (1 << LUA_TNUMBER) | (1 << LUA_TSTRING); | ||
1609 | STACK_GROW(L2, 1); | ||
1610 | STACK_CHECK_START_REL(L1, 0); | ||
1611 | STACK_CHECK_START_REL(L2, 0); | ||
1612 | |||
1613 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "inter_copy_one()\n" INDENT_END(U))); | ||
1614 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
1615 | |||
1616 | LuaType val_type{ lua_type_as_enum(L1, L1_i) }; | ||
1617 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "%s %s: " INDENT_END(U), lua_type_names[static_cast<int>(val_type)], vt_names[static_cast<int>(vt)])); | ||
1618 | |||
1619 | // Non-POD can be skipped if its metatable contains { __lanesignore = true } | ||
1620 | if (((1 << static_cast<int>(val_type)) & kPODmask) == 0) { | ||
1621 | if (lua_getmetatable(L1, L1_i)) { // L1: ... mt | ||
1622 | lua_getfield(L1, -1, "__lanesignore"); // L1: ... mt ignore? | ||
1623 | if (lua_isboolean(L1, -1) && lua_toboolean(L1, -1)) { | ||
1624 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "__lanesignore -> LUA_TNIL\n" INDENT_END(U))); | ||
1625 | val_type = LuaType::NIL; | ||
1626 | } | ||
1627 | lua_pop(L1, 2); // L1: ... | ||
1628 | } | ||
1629 | } | ||
1630 | STACK_CHECK(L1, 0); | ||
1631 | |||
1632 | // Lets push nil to L2 if the object should be ignored | ||
1633 | bool ret{ true }; | ||
1634 | switch (val_type) { | ||
1635 | // Basic types allowed both as values, and as table keys | ||
1636 | case LuaType::BOOLEAN: | ||
1637 | ret = inter_copy_boolean(); | ||
1638 | break; | ||
1639 | case LuaType::NUMBER: | ||
1640 | ret = inter_copy_number(); | ||
1641 | break; | ||
1642 | case LuaType::STRING: | ||
1643 | ret = inter_copy_string(); | ||
1644 | break; | ||
1645 | case LuaType::LIGHTUSERDATA: | ||
1646 | ret = inter_copy_lightuserdata(); | ||
1647 | break; | ||
1648 | |||
1649 | // The following types are not allowed as table keys | ||
1650 | case LuaType::USERDATA: | ||
1651 | ret = inter_copy_userdata(); | ||
1652 | break; | ||
1653 | case LuaType::NIL: | ||
1654 | ret = inter_copy_nil(); | ||
1655 | break; | ||
1656 | case LuaType::FUNCTION: | ||
1657 | ret = inter_copy_function(); | ||
1658 | break; | ||
1659 | case LuaType::TABLE: | ||
1660 | ret = inter_copy_table(); | ||
1661 | break; | ||
1662 | |||
1663 | // The following types cannot be copied | ||
1664 | case LuaType::CDATA: | ||
1665 | [[fallthrough]]; | ||
1666 | case LuaType::THREAD: | ||
1667 | ret = false; | ||
1668 | break; | ||
1669 | } | ||
1670 | |||
1671 | STACK_CHECK(L2, ret ? 1 : 0); | ||
1672 | STACK_CHECK(L1, 0); | ||
1673 | return ret; | ||
1674 | } | ||
1675 | |||
1676 | // ################################################################################################# | ||
1677 | |||
1678 | // Akin to 'lua_xmove' but copies values between _any_ Lua states. | ||
1679 | // NOTE: Both the states must be solely in the current OS thread's possession. | ||
1680 | [[nodiscard]] InterCopyResult InterCopyContext::inter_copy(int n_) const | ||
1681 | { | ||
1682 | LUA_ASSERT(L1, vt == VT::NORMAL); | ||
1683 | |||
1684 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "InterCopyContext::inter_copy()\n" INDENT_END(U))); | ||
1685 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
1686 | |||
1687 | int const top_L1{ lua_gettop(L1) }; | ||
1688 | if (n_ > top_L1) { | ||
1689 | // requesting to copy more than is available? | ||
1690 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "nothing to copy()\n" INDENT_END(U))); | ||
1691 | return InterCopyResult::NotEnoughValues; | ||
1692 | } | ||
1693 | |||
1694 | STACK_CHECK_START_REL(L2, 0); | ||
1695 | STACK_GROW(L2, n_ + 1); | ||
1696 | |||
1697 | /* | ||
1698 | * Make a cache table for the duration of this copy. Collects tables and | ||
1699 | * function entries, avoiding the same entries to be passed on as multiple | ||
1700 | * copies. ESSENTIAL i.e. for handling upvalue tables in the right manner! | ||
1701 | */ | ||
1702 | int const top_L2{ lua_gettop(L2) }; // L2: ... | ||
1703 | lua_newtable(L2); // L2: ... cache | ||
1704 | |||
1705 | char tmpBuf[16]; | ||
1706 | char const* const pBuf{ U->verboseErrors ? tmpBuf : "?" }; | ||
1707 | InterCopyContext c{ U, L2, L1, CacheIndex{ top_L2 + 1 }, {}, VT::NORMAL, mode, pBuf }; | ||
1708 | bool copyok{ true }; | ||
1709 | STACK_CHECK_START_REL(L1, 0); | ||
1710 | for (int i{ top_L1 - n_ + 1 }, j{ 1 }; i <= top_L1; ++i, ++j) { | ||
1711 | if (U->verboseErrors) { | ||
1712 | sprintf(tmpBuf, "arg_%d", j); | ||
1713 | } | ||
1714 | c.L1_i = SourceIndex{ i }; | ||
1715 | copyok = c.inter_copy_one(); // L2: ... cache {}n | ||
1716 | if (!copyok) { | ||
1717 | break; | ||
1718 | } | ||
1719 | } | ||
1720 | STACK_CHECK(L1, 0); | ||
1721 | |||
1722 | if (copyok) { | ||
1723 | STACK_CHECK(L2, n_ + 1); | ||
1724 | // Remove the cache table. Persistent caching would cause i.e. multiple | ||
1725 | // messages passed in the same table to use the same table also in receiving end. | ||
1726 | lua_remove(L2, top_L2 + 1); | ||
1727 | return InterCopyResult::Success; | ||
1728 | } | ||
1729 | |||
1730 | // error -> pop everything from the target state stack | ||
1731 | lua_settop(L2, top_L2); | ||
1732 | STACK_CHECK(L2, 0); | ||
1733 | return InterCopyResult::Error; | ||
1734 | } | ||
1735 | |||
1736 | // ################################################################################################# | ||
1737 | |||
1738 | [[nodiscard]] InterCopyResult InterCopyContext::inter_move(int n_) const | ||
1739 | { | ||
1740 | InterCopyResult const ret{ inter_copy(n_) }; | ||
1741 | lua_pop(L1, n_); | ||
1742 | return ret; | ||
1743 | } | ||
1744 | |||
1745 | // ################################################################################################# | ||
1746 | |||
1747 | // transfers stuff from L1->_G["package"] to L2->_G["package"] | ||
1748 | // returns InterCopyResult::Success if everything is fine | ||
1749 | // returns InterCopyResult::Error if pushed an error message in L1 | ||
1750 | // else raise an error in L1 | ||
1751 | [[nodiscard]] InterCopyResult InterCopyContext::inter_copy_package() const | ||
1752 | { | ||
1753 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "InterCopyContext::inter_copy_package()\n" INDENT_END(U))); | ||
1754 | |||
1755 | class OnExit | ||
1756 | { | ||
1757 | private: | ||
1758 | lua_State* const L2; | ||
1759 | int const top_L2; | ||
1760 | DEBUGSPEW_CODE(DebugSpewIndentScope m_scope); | ||
1761 | |||
1762 | public: | ||
1763 | OnExit(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L2_) | ||
1764 | : L2{ L2_ } | ||
1765 | , top_L2{ lua_gettop(L2) } DEBUGSPEW_COMMA_PARAM(m_scope{ U_ }) | ||
1766 | { | ||
1767 | } | ||
1768 | |||
1769 | ~OnExit() | ||
1770 | { | ||
1771 | lua_settop(L2, top_L2); | ||
1772 | } | ||
1773 | } onExit{ DEBUGSPEW_PARAM_COMMA(U) L2 }; | ||
1774 | |||
1775 | STACK_CHECK_START_REL(L1, 0); | ||
1776 | if (lua_type_as_enum(L1, L1_i) != LuaType::TABLE) { | ||
1777 | lua_pushfstring(L1, "expected package as table, got %s", luaL_typename(L1, L1_i)); | ||
1778 | STACK_CHECK(L1, 1); | ||
1779 | // raise the error when copying from lane to lane, else just leave it on the stack to be raised later | ||
1780 | if (mode == LookupMode::LaneBody) { | ||
1781 | raise_lua_error(getErrL()); // that's ok, getErrL() is L1 in that case | ||
1782 | } | ||
1783 | return InterCopyResult::Error; | ||
1784 | } | ||
1785 | if (luaG_getmodule(L2, LUA_LOADLIBNAME) == LuaType::NIL) { // package library not loaded: do nothing | ||
1786 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "'package' not loaded, nothing to do\n" INDENT_END(U))); | ||
1787 | STACK_CHECK(L1, 0); | ||
1788 | return InterCopyResult::Success; | ||
1789 | } | ||
1790 | |||
1791 | InterCopyResult result{ InterCopyResult::Success }; | ||
1792 | // package.loaders is renamed package.searchers in Lua 5.2 | ||
1793 | // but don't copy it anyway, as the function names change depending on the slot index! | ||
1794 | // users should provide an on_state_create function to setup custom loaders instead | ||
1795 | // don't copy package.preload in keeper states (they don't know how to translate functions) | ||
1796 | char const* entries[] = { "path", "cpath", (mode == LookupMode::LaneBody) ? "preload" : nullptr /*, (LUA_VERSION_NUM == 501) ? "loaders" : "searchers"*/, nullptr }; | ||
1797 | for (char const* const entry : entries) { | ||
1798 | if (!entry) { | ||
1799 | continue; | ||
1800 | } | ||
1801 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "package.%s\n" INDENT_END(U), entry)); | ||
1802 | lua_getfield(L1, L1_i, entry); | ||
1803 | if (lua_isnil(L1, -1)) { | ||
1804 | lua_pop(L1, 1); | ||
1805 | } else { | ||
1806 | { | ||
1807 | DEBUGSPEW_CODE(DebugSpewIndentScope scope{ U }); | ||
1808 | result = inter_move(1); // moves the entry to L2 | ||
1809 | STACK_CHECK(L1, 0); | ||
1810 | } | ||
1811 | if (result == InterCopyResult::Success) { | ||
1812 | lua_setfield(L2, -2, entry); // set package[entry] | ||
1813 | } else { | ||
1814 | lua_pushfstring(L1, "failed to copy package entry %s", entry); | ||
1815 | // raise the error when copying from lane to lane, else just leave it on the stack to be raised later | ||
1816 | if (mode == LookupMode::LaneBody) { | ||
1817 | raise_lua_error(getErrL()); | ||
1818 | } | ||
1819 | lua_pop(L1, 1); | ||
1820 | break; | ||
1821 | } | ||
1822 | } | ||
1823 | } | ||
1824 | STACK_CHECK(L1, 0); | ||
1825 | return result; | ||
1826 | } | ||
diff --git a/src/tools.h b/src/tools.h index 53d3a99..be76fd9 100644 --- a/src/tools.h +++ b/src/tools.h | |||
@@ -1,88 +1,30 @@ | |||
1 | #pragma once | 1 | #pragma once |
2 | 2 | ||
3 | #include "deep.h" | 3 | #include "uniquekey.h" |
4 | #include "macros_and_utils.h" | ||
5 | 4 | ||
6 | // forwards | ||
7 | class Universe; | 5 | class Universe; |
8 | 6 | ||
9 | // ################################################################################################# | 7 | enum class LookupMode |
10 | |||
11 | enum class VT | ||
12 | { | 8 | { |
13 | NORMAL, // keep this one first so that it's the value we get when we default-construct | 9 | LaneBody, // send the lane body directly from the source to the destination lane. keep this one first so that it's the value we get when we default-construct |
14 | KEY, | 10 | ToKeeper, // send a function from a lane to a keeper state |
15 | METATABLE | 11 | FromKeeper // send a function from a keeper state to a lane |
16 | }; | 12 | }; |
17 | 13 | ||
18 | enum class InterCopyResult | 14 | enum class FuncSubType |
19 | { | 15 | { |
20 | Success, | 16 | Bytecode, |
21 | NotEnoughValues, | 17 | Native, |
22 | Error | 18 | FastJIT |
23 | }; | 19 | }; |
24 | 20 | ||
25 | // ################################################################################################# | 21 | [[nodiscard]] FuncSubType luaG_getfuncsubtype(lua_State* L_, int _i); |
26 | |||
27 | using CacheIndex = Unique<int>; | ||
28 | using SourceIndex = Unique<int>; | ||
29 | class InterCopyContext | ||
30 | { | ||
31 | public: | ||
32 | Universe* const U; | ||
33 | DestState const L2; | ||
34 | SourceState const L1; | ||
35 | CacheIndex const L2_cache_i; | ||
36 | SourceIndex L1_i; // that one can change when we reuse the context | ||
37 | VT vt; // that one can change when we reuse the context | ||
38 | LookupMode const mode; | ||
39 | char const* name; // that one can change when we reuse the context | ||
40 | |||
41 | private: | ||
42 | // when mode == LookupMode::FromKeeper, L1 is a keeper state and L2 is not, therefore L2 is the state where we want to raise the error | ||
43 | // whon mode != LookupMode::FromKeeper, L1 is not a keeper state, therefore L1 is the state where we want to raise the error | ||
44 | lua_State* getErrL() const { return (mode == LookupMode::FromKeeper) ? L2 : L1; } | ||
45 | |||
46 | // for use in copy_cached_func | ||
47 | void copy_func() const; | ||
48 | void lookup_native_func() const; | ||
49 | |||
50 | // for use in inter_copy_function | ||
51 | void copy_cached_func() const; | ||
52 | [[nodiscard]] bool lookup_table() const; | ||
53 | |||
54 | // for use in inter_copy_table | ||
55 | void inter_copy_keyvaluepair() const; | ||
56 | [[nodiscard]] bool push_cached_metatable() const; | ||
57 | [[nodiscard]] bool push_cached_table() const; | ||
58 | |||
59 | // for use in inter_copy_userdata | ||
60 | [[nodiscard]] bool tryCopyClonable() const; | ||
61 | [[nodiscard]] bool tryCopyDeep() const; | ||
62 | |||
63 | // copying a single Lua stack item | ||
64 | [[nodiscard]] bool inter_copy_boolean() const; | ||
65 | [[nodiscard]] bool inter_copy_function() const; | ||
66 | [[nodiscard]] bool inter_copy_lightuserdata() const; | ||
67 | [[nodiscard]] bool inter_copy_nil() const; | ||
68 | [[nodiscard]] bool inter_copy_number() const; | ||
69 | [[nodiscard]] bool inter_copy_string() const; | ||
70 | [[nodiscard]] bool inter_copy_table() const; | ||
71 | [[nodiscard]] bool inter_copy_userdata() const; | ||
72 | |||
73 | public: | ||
74 | [[nodiscard]] bool inter_copy_one() const; | ||
75 | [[nodiscard]] InterCopyResult inter_copy_package() const; | ||
76 | [[nodiscard]] InterCopyResult inter_copy(int n_) const; | ||
77 | [[nodiscard]] InterCopyResult inter_move(int n_) const; | ||
78 | }; | ||
79 | 22 | ||
80 | // ################################################################################################# | 23 | // ################################################################################################# |
81 | 24 | ||
82 | [[nodiscard]] int luaG_nameof(lua_State* L_); | 25 | [[nodiscard]] int luaG_nameof(lua_State* L_); |
83 | 26 | ||
84 | void populate_func_lookup_table(lua_State* L_, int i_, char const* name_); | 27 | void populate_func_lookup_table(lua_State* L_, int i_, char const* name_); |
85 | void initialize_allocator_function(Universe* U_, lua_State* L_); | ||
86 | 28 | ||
87 | // ################################################################################################# | 29 | // ################################################################################################# |
88 | 30 | ||
diff --git a/src/universe.cpp b/src/universe.cpp index becffdd..a5c28f0 100644 --- a/src/universe.cpp +++ b/src/universe.cpp | |||
@@ -30,12 +30,9 @@ THE SOFTWARE. | |||
30 | 30 | ||
31 | #include "universe.h" | 31 | #include "universe.h" |
32 | 32 | ||
33 | #include "cancel.h" | ||
34 | #include "compat.h" | ||
35 | #include "deep.h" | 33 | #include "deep.h" |
36 | #include "keeper.h" | 34 | #include "keeper.h" |
37 | #include "lanes_private.h" | 35 | #include "lanes_private.h" |
38 | #include "tools.h" | ||
39 | 36 | ||
40 | // ################################################################################################# | 37 | // ################################################################################################# |
41 | 38 | ||
diff --git a/src/universe.h b/src/universe.h index 58ddffc..3adba4d 100644 --- a/src/universe.h +++ b/src/universe.h | |||
@@ -9,7 +9,6 @@ extern "C" | |||
9 | } | 9 | } |
10 | #endif // __cplusplus | 10 | #endif // __cplusplus |
11 | 11 | ||
12 | #include "compat.h" | ||
13 | #include "macros_and_utils.h" | 12 | #include "macros_and_utils.h" |
14 | #include "uniquekey.h" | 13 | #include "uniquekey.h" |
15 | 14 | ||