aboutsummaryrefslogtreecommitdiff
path: root/src/deep.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/deep.cpp')
-rw-r--r--src/deep.cpp494
1 files changed, 494 insertions, 0 deletions
diff --git a/src/deep.cpp b/src/deep.cpp
new file mode 100644
index 0000000..d0b8123
--- /dev/null
+++ b/src/deep.cpp
@@ -0,0 +1,494 @@
1/*
2 * DEEP.CPP Copyright (c) 2024, Benoit Germain
3 *
4 * Deep userdata support, separate in its own source file to help integration
5 * without enforcing a Lanes dependency
6 */
7
8/*
9===============================================================================
10
11Copyright (C) 2002-10 Asko Kauppi <akauppi@gmail.com>
12 2011-24 Benoit Germain <bnt.germain@gmail.com>
13
14Permission is hereby granted, free of charge, to any person obtaining a copy
15of this software and associated documentation files (the "Software"), to deal
16in the Software without restriction, including without limitation the rights
17to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18copies of the Software, and to permit persons to whom the Software is
19furnished to do so, subject to the following conditions:
20
21The above copyright notice and this permission notice shall be included in
22all copies or substantial portions of the Software.
23
24THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30THE SOFTWARE.
31
32===============================================================================
33*/
34
35#include "deep.h"
36
37#include "compat.h"
38#include "tools.h"
39#include "uniquekey.h"
40#include "universe.h"
41
42#include <bit>
43#include <cassert>
44
45/*-- Metatable copying --*/
46
47/*---=== Deep userdata ===---*/
48
49/*
50* 'registry[REGKEY]' is a two-way lookup table for 'idfunc's and those type's
51* metatables:
52*
53* metatable -> idfunc
54* idfunc -> metatable
55*/
56// crc64/we of string "DEEP_LOOKUP_KEY" generated at http://www.nitrxgen.net/hashgen/
57static constexpr UniqueKey DEEP_LOOKUP_KEY{ 0x9fb9b4f3f633d83dull };
58
59/*
60 * The deep proxy cache is a weak valued table listing all deep UD proxies indexed by the deep UD that they are proxying
61 * crc64/we of string "DEEP_PROXY_CACHE_KEY" generated at http://www.nitrxgen.net/hashgen/
62*/
63static constexpr UniqueKey DEEP_PROXY_CACHE_KEY{ 0x05773d6fc26be106ull };
64
65/*
66* Sets up [-1]<->[-2] two-way lookups, and ensures the lookup table exists.
67* Pops the both values off the stack.
68*/
69static void set_deep_lookup(lua_State* L)
70{
71 STACK_GROW( L, 3);
72 STACK_CHECK_START_REL(L, 2); // a b
73 push_registry_subtable( L, DEEP_LOOKUP_KEY); // a b {}
74 STACK_CHECK( L, 3);
75 lua_insert( L, -3); // {} a b
76 lua_pushvalue( L, -1); // {} a b b
77 lua_pushvalue( L,-3); // {} a b b a
78 lua_rawset( L, -5); // {} a b
79 lua_rawset( L, -3); // {}
80 lua_pop( L, 1); //
81 STACK_CHECK( L, 0);
82}
83
84// ################################################################################################
85
86/*
87* Pops the key (metatable or idfunc) off the stack, and replaces with the
88* deep lookup value (idfunc/metatable/nil).
89*/
90static void get_deep_lookup(lua_State* L)
91{
92 STACK_GROW( L, 1);
93 STACK_CHECK_START_REL(L, 1); // a
94 DEEP_LOOKUP_KEY.pushValue(L); // a {}
95 if( !lua_isnil( L, -1))
96 {
97 lua_insert( L, -2); // {} a
98 lua_rawget( L, -2); // {} b
99 }
100 lua_remove( L, -2); // a|b
101 STACK_CHECK( L, 1);
102}
103
104// ################################################################################################
105
106/*
107* Return the registered ID function for 'index' (deep userdata proxy),
108* or nullptr if 'index' is not a deep userdata proxy.
109*/
110[[nodiscard]] static inline luaG_IdFunction get_idfunc(lua_State* L, int index, LookupMode mode_)
111{
112 // when looking inside a keeper, we are 100% sure the object is a deep userdata
113 if (mode_ == LookupMode::FromKeeper)
114 {
115 DeepPrelude** const proxy{ lua_tofulluserdata<DeepPrelude*>(L, index) };
116 // we can (and must) cast and fetch the internally stored idfunc
117 return (*proxy)->idfunc;
118 }
119 else
120 {
121 // essentially we are making sure that the metatable of the object we want to copy is stored in our metatable/idfunc database
122 // it is the only way to ensure that the userdata is indeed a deep userdata!
123 // of course, we could just trust the caller, but we won't
124 STACK_GROW( L, 1);
125 STACK_CHECK_START_REL(L, 0);
126
127 if( !lua_getmetatable( L, index)) // deep ... metatable?
128 {
129 return nullptr; // no metatable: can't be a deep userdata object!
130 }
131
132 // replace metatable with the idfunc pointer, if it is actually a deep userdata
133 get_deep_lookup( L); // deep ... idfunc|nil
134
135 luaG_IdFunction const ret{ *lua_tolightuserdata<luaG_IdFunction>(L, -1) }; // nullptr if not a userdata
136 lua_pop( L, 1);
137 STACK_CHECK( L, 0);
138 return ret;
139 }
140}
141
142// ################################################################################################
143
144void free_deep_prelude(lua_State* L, DeepPrelude* prelude_)
145{
146 ASSERT_L(prelude_->idfunc);
147 STACK_CHECK_START_REL(L, 0);
148 // Call 'idfunc( "delete", deep_ptr )' to make deep cleanup
149 lua_pushlightuserdata( L, prelude_);
150 prelude_->idfunc( L, DeepOp::Delete);
151 lua_pop(L, 1);
152 STACK_CHECK(L, 0);
153}
154
155// ################################################################################################
156
157/*
158 * void= mt.__gc( proxy_ud )
159 *
160 * End of life for a proxy object; reduce the deep reference count and clean it up if reaches 0.
161 *
162 */
163[[nodiscard]] static int deep_userdata_gc(lua_State* L)
164{
165 DeepPrelude** const proxy{ lua_tofulluserdata<DeepPrelude*>(L, 1) };
166 DeepPrelude* p = *proxy;
167
168 // can work without a universe if creating a deep userdata from some external C module when Lanes isn't loaded
169 // in that case, we are not multithreaded and locking isn't necessary anyway
170 bool const isLastRef{ p->m_refcount.fetch_sub(1, std::memory_order_relaxed) == 1 };
171
172 if (isLastRef)
173 {
174 // retrieve wrapped __gc
175 lua_pushvalue( L, lua_upvalueindex( 1)); // self __gc?
176 if( !lua_isnil( L, -1))
177 {
178 lua_insert( L, -2); // __gc self
179 lua_call( L, 1, 0); //
180 }
181 // 'idfunc' expects a clean stack to work on
182 lua_settop( L, 0);
183 free_deep_prelude( L, p);
184
185 // top was set to 0, then userdata was pushed. "delete" might want to pop the userdata (we don't care), but should not push anything!
186 if ( lua_gettop( L) > 1)
187 {
188 return luaL_error( L, "Bad idfunc(DeepOp::Delete): should not push anything");
189 }
190 }
191 *proxy = nullptr; // make sure we don't use it any more, just in case
192 return 0;
193}
194
195// ################################################################################################
196
197/*
198 * Push a proxy userdata on the stack.
199 * returns nullptr if ok, else some error string related to bad idfunc behavior or module require problem
200 * (error cannot happen with mode_ == LookupMode::ToKeeper)
201 *
202 * Initializes necessary structures if it's the first time 'idfunc' is being
203 * used in this Lua state (metatable, registring it). Otherwise, increments the
204 * reference count.
205 */
206char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_)
207{
208 // Check if a proxy already exists
209 push_registry_subtable_mode( L, DEEP_PROXY_CACHE_KEY, "v"); // DPC
210 lua_pushlightuserdata( L, prelude); // DPC deep
211 lua_rawget( L, -2); // DPC proxy
212 if ( !lua_isnil( L, -1))
213 {
214 lua_remove( L, -2); // proxy
215 return nullptr;
216 }
217 else
218 {
219 lua_pop( L, 1); // DPC
220 }
221
222 STACK_GROW( L, 7);
223 STACK_CHECK_START_REL(L, 0);
224
225 // a new full userdata, fitted with the specified number of uservalue slots (always 1 for Lua < 5.4)
226 DeepPrelude** proxy = (DeepPrelude**) lua_newuserdatauv(L, sizeof(DeepPrelude*), nuv_); // DPC proxy
227 ASSERT_L( proxy);
228 *proxy = prelude;
229 prelude->m_refcount.fetch_add(1, std::memory_order_relaxed); // one more proxy pointing to this deep data
230
231 // Get/create metatable for 'idfunc' (in this state)
232 lua_pushlightuserdata( L, std::bit_cast<void*>(prelude->idfunc)); // DPC proxy idfunc
233 get_deep_lookup( L); // DPC proxy metatable?
234
235 if( lua_isnil( L, -1)) // // No metatable yet.
236 {
237 char const* modname;
238 int oldtop = lua_gettop( L); // DPC proxy nil
239 lua_pop( L, 1); // DPC proxy
240 // 1 - make one and register it
241 if (mode_ != LookupMode::ToKeeper)
242 {
243 (void) prelude->idfunc( L, DeepOp::Metatable); // DPC proxy metatable
244 if( lua_gettop( L) - oldtop != 0 || !lua_istable( L, -1))
245 {
246 lua_settop( L, oldtop); // DPC proxy X
247 lua_pop( L, 3); //
248 return "Bad idfunc(eOP_metatable): unexpected pushed value";
249 }
250 // if the metatable contains a __gc, we will call it from our own
251 lua_getfield( L, -1, "__gc"); // DPC proxy metatable __gc
252 }
253 else
254 {
255 // keepers need a minimal metatable that only contains our own __gc
256 lua_newtable( L); // DPC proxy metatable
257 lua_pushnil( L); // DPC proxy metatable nil
258 }
259 if( lua_isnil( L, -1))
260 {
261 // Add our own '__gc' method
262 lua_pop( L, 1); // DPC proxy metatable
263 lua_pushcfunction( L, deep_userdata_gc); // DPC proxy metatable deep_userdata_gc
264 }
265 else
266 {
267 // Add our own '__gc' method wrapping the original
268 lua_pushcclosure( L, deep_userdata_gc, 1); // DPC proxy metatable deep_userdata_gc
269 }
270 lua_setfield( L, -2, "__gc"); // DPC proxy metatable
271
272 // Memorize for later rounds
273 lua_pushvalue( L, -1); // DPC proxy metatable metatable
274 lua_pushlightuserdata( L, std::bit_cast<void*>(prelude->idfunc)); // DPC proxy metatable metatable idfunc
275 set_deep_lookup( L); // DPC proxy metatable
276
277 // 2 - cause the target state to require the module that exported the idfunc
278 // this is needed because we must make sure the shared library is still loaded as long as we hold a pointer on the idfunc
279 {
280 int oldtop_module = lua_gettop( L);
281 modname = (char const*) prelude->idfunc( L, DeepOp::Module); // DPC proxy metatable
282 // make sure the function pushed nothing on the stack!
283 if( lua_gettop( L) - oldtop_module != 0)
284 {
285 lua_pop( L, 3); //
286 return "Bad idfunc(eOP_module): should not push anything";
287 }
288 }
289 if (nullptr != modname) // we actually got a module name
290 {
291 // L.registry._LOADED exists without having registered the 'package' library.
292 lua_getglobal( L, "require"); // DPC proxy metatable require()
293 // check that the module is already loaded (or being loaded, we are happy either way)
294 if( lua_isfunction( L, -1))
295 {
296 lua_pushstring( L, modname); // DPC proxy metatable require() "module"
297 lua_getfield( L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE); // DPC proxy metatable require() "module" _R._LOADED
298 if( lua_istable( L, -1))
299 {
300 lua_pushvalue( L, -2); // DPC proxy metatable require() "module" _R._LOADED "module"
301 lua_rawget( L, -2); // DPC proxy metatable require() "module" _R._LOADED module
302 int const alreadyloaded = lua_toboolean( L, -1);
303 if( !alreadyloaded) // not loaded
304 {
305 int require_result;
306 lua_pop( L, 2); // DPC proxy metatable require() "module"
307 // require "modname"
308 require_result = lua_pcall( L, 1, 0, 0); // DPC proxy metatable error?
309 if( require_result != LUA_OK)
310 {
311 // failed, return the error message
312 lua_pushfstring( L, "error while requiring '%s' identified by idfunc(eOP_module): ", modname);
313 lua_insert( L, -2); // DPC proxy metatable prefix error
314 lua_concat( L, 2); // DPC proxy metatable error
315 return lua_tostring( L, -1);
316 }
317 }
318 else // already loaded, we are happy
319 {
320 lua_pop( L, 4); // DPC proxy metatable
321 }
322 }
323 else // no L.registry._LOADED; can this ever happen?
324 {
325 lua_pop( L, 6); //
326 return "unexpected error while requiring a module identified by idfunc(eOP_module)";
327 }
328 }
329 else // a module name, but no require() function :-(
330 {
331 lua_pop( L, 4); //
332 return "lanes receiving deep userdata should register the 'package' library";
333 }
334 }
335 }
336 STACK_CHECK(L, 2); // DPC proxy metatable
337 ASSERT_L(lua_type(L, -2) == LUA_TUSERDATA);
338 ASSERT_L(lua_istable( L, -1));
339 lua_setmetatable( L, -2); // DPC proxy
340
341 // If we're here, we obviously had to create a new proxy, so cache it.
342 lua_pushlightuserdata( L, prelude); // DPC proxy deep
343 lua_pushvalue( L, -2); // DPC proxy deep proxy
344 lua_rawset( L, -4); // DPC proxy
345 lua_remove( L, -2); // proxy
346 ASSERT_L(lua_type(L, -1) == LUA_TUSERDATA);
347 STACK_CHECK(L, 0);
348 return nullptr;
349}
350
351// ################################################################################################
352
353/*
354* Create a deep userdata
355*
356* proxy_ud= deep_userdata( idfunc [, ...] )
357*
358* Creates a deep userdata entry of the type defined by 'idfunc'.
359* Parameters found on the stack are left as is passed on to the 'idfunc' "new" invocation.
360*
361* 'idfunc' must fulfill the following features:
362*
363* lightuserdata = idfunc( DeepOp::New [, ...] ) -- creates a new deep data instance
364* void = idfunc( DeepOp::Delete, lightuserdata ) -- releases a deep data instance
365* tbl = idfunc( DeepOp::Metatable ) -- gives metatable for userdata proxies
366*
367* Reference counting and true userdata proxying are taken care of for the
368* actual data type.
369*
370* Types using the deep userdata system (and only those!) can be passed between
371* separate Lua states via 'luaG_inter_move()'.
372*
373* Returns: 'proxy' userdata for accessing the deep data via 'luaG_todeep()'
374*/
375int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_)
376{
377 STACK_GROW( L, 1);
378 STACK_CHECK_START_REL(L, 0);
379 int const oldtop{ lua_gettop(L) };
380 DeepPrelude* const prelude{ static_cast<DeepPrelude*>(idfunc(L, DeepOp::New)) };
381 if (prelude == nullptr)
382 {
383 return luaL_error( L, "idfunc(DeepOp::New) failed to create deep userdata (out of memory)");
384 }
385
386 if( prelude->magic != DEEP_VERSION)
387 {
388 // just in case, don't leak the newly allocated deep userdata object
389 lua_pushlightuserdata( L, prelude);
390 idfunc( L, DeepOp::Delete);
391 return luaL_error( L, "Bad idfunc(DeepOp::New): DEEP_VERSION is incorrect, rebuild your implementation with the latest deep implementation");
392 }
393
394 ASSERT_L(prelude->m_refcount.load(std::memory_order_relaxed) == 0); // 'push_deep_proxy' will lift it to 1
395 prelude->idfunc = idfunc;
396
397 if( lua_gettop( L) - oldtop != 0)
398 {
399 // just in case, don't leak the newly allocated deep userdata object
400 lua_pushlightuserdata( L, prelude);
401 idfunc( L, DeepOp::Delete);
402 return luaL_error( L, "Bad idfunc(DeepOp::New): should not push anything on the stack");
403 }
404
405 char const* const errmsg{ push_deep_proxy(L, prelude, nuv_, LookupMode::LaneBody) }; // proxy
406 if (errmsg != nullptr)
407 {
408 return luaL_error( L, errmsg);
409 }
410 STACK_CHECK( L, 1);
411 return 1;
412}
413
414// ################################################################################################
415
416/*
417* Access deep userdata through a proxy.
418*
419* Reference count is not changed, and access to the deep userdata is not
420* serialized. It is the module's responsibility to prevent conflicting usage.
421*/
422DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index)
423{
424 STACK_CHECK_START_REL(L, 0);
425 // ensure it is actually a deep userdata
426 if (get_idfunc(L, index, LookupMode::LaneBody) != idfunc)
427 {
428 return nullptr; // no metatable, or wrong kind
429 }
430 STACK_CHECK(L, 0);
431
432 DeepPrelude** const proxy{ lua_tofulluserdata<DeepPrelude*>(L, index) };
433 return *proxy;
434}
435
436// ################################################################################################
437
438/*
439 * Copy deep userdata between two separate Lua states (from L to L2)
440 *
441 * Returns:
442 * the id function of the copied value, or nullptr for non-deep userdata
443 * (not copied)
444 */
445bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_)
446{
447 luaG_IdFunction const idfunc { get_idfunc(L, i, mode_) };
448 if (idfunc == nullptr)
449 {
450 return false; // not a deep userdata
451 }
452
453 STACK_CHECK_START_REL(L, 0);
454 STACK_CHECK_START_REL(L2, 0);
455
456 // extract all uservalues of the source
457 int nuv = 0;
458 while (lua_getiuservalue(L, i, nuv + 1) != LUA_TNONE) // ... u [uv]* nil
459 {
460 ++ nuv;
461 }
462 // last call returned TNONE and pushed nil, that we don't need
463 lua_pop( L, 1); // ... u [uv]*
464 STACK_CHECK( L, nuv);
465
466 char const* errmsg{ push_deep_proxy(L2, *lua_tofulluserdata<DeepPrelude*>(L, i), nuv, mode_) }; // u
467
468 // transfer all uservalues of the source in the destination
469 {
470 int const clone_i = lua_gettop( L2);
471 while( nuv)
472 {
473 if (!inter_copy_one(U, L2, L2_cache_i, L, lua_absindex(L, -1), VT::NORMAL, mode_, upName_)) // u uv
474 {
475 return luaL_error(L, "Cannot copy upvalue type '%s'", luaL_typename(L, -1));
476 }
477 lua_pop( L, 1); // ... u [uv]*
478 // this pops the value from the stack
479 lua_setiuservalue(L2, clone_i, nuv); // u
480 -- nuv;
481 }
482 }
483
484 STACK_CHECK(L2, 1);
485 STACK_CHECK(L, 0);
486
487 if (errmsg != nullptr)
488 {
489 // raise the error in the proper state (not the keeper)
490 lua_State* const errL{ (mode_ == LookupMode::FromKeeper) ? L2 : L };
491 luaL_error(errL, errmsg); // doesn't return
492 }
493 return true;
494} \ No newline at end of file