3 Copyright (C) 2022 sfan5 <sfan5@live.de>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include <unordered_set>
25 #include <unordered_map>
27 #include "c_internal.h"
30 #include "threading/mutex_auto_lock.h"
40 // convert negative index to absolute position on Lua stack
41 static inline int absidx(lua_State *L, int idx)
44 return lua_gettop(L) + idx + 1;
47 // does the type put anything into PackedInstr::sdata?
48 static inline bool uses_sdata(int type)
60 // does the type put anything into PackedInstr::<union>?
61 static inline bool uses_union(int type)
73 static inline bool can_set_into(int ktype, int vtype)
77 return !uses_union(vtype);
79 return !uses_sdata(vtype);
85 // is the key suitable for use with set_into?
86 static inline bool suitable_key(lua_State *L, int idx)
88 if (lua_type(L, idx) == LUA_TSTRING) {
89 // strings may not have a NULL byte (-> lua_setfield)
91 const char *str = lua_tolstring(L, idx, &len);
92 return strlen(str) == len;
94 assert(lua_type(L, idx) == LUA_TNUMBER);
95 // numbers must fit into an s32 and be integers (-> lua_rawseti)
96 lua_Number n = lua_tonumber(L, idx);
97 return std::floor(n) == n && n >= S32_MIN && n <= S32_MAX;
102 // checks if you left any values on the stack, for debugging
107 StackChecker(lua_State *L) : L(L), top(lua_gettop(L)) {}
109 assert(lua_gettop(L) >= top);
110 if (lua_gettop(L) > top) {
111 rawstream << "Lua stack not cleaned up: "
112 << lua_gettop(L) << " != " << top
113 << " (false-positive if exception thrown)" << std::endl;
118 // Since an std::vector may reallocate, this is the only safe way to keep
119 // a reference to a particular element.
120 template <typename T>
124 VectorRef(std::vector<T> *vec, size_t idx) : vec(vec), idx(idx) {}
126 constexpr VectorRef() : vec(nullptr), idx(0) {}
127 static VectorRef<T> front(std::vector<T> &vec) {
128 return VectorRef(&vec, 0);
130 static VectorRef<T> back(std::vector<T> &vec) {
131 return VectorRef(&vec, vec.size() - 1);
133 T &operator*() { return (*vec)[idx]; }
134 T *operator->() { return &(*vec)[idx]; }
135 operator bool() const { return vec != nullptr; }
143 typedef std::pair<std::string, Packer> PackerTuple;
146 static inline auto emplace(PackedValue &pv, s16 type)
149 auto ref = VectorRef<PackedInstr>::back(pv.i);
151 // Initialize fields that may be left untouched
152 if (type == LUA_TTABLE) {
155 } else if (type == LUA_TUSERDATA) {
156 ref->ptrdata = nullptr;
157 } else if (type == INSTR_POP) {
164 // Management of registered packers
167 static std::unordered_map<std::string, Packer> g_packers;
168 static std::mutex g_packers_lock;
170 void script_register_packer(lua_State *L, const char *regname,
171 PackInFunc fin, PackOutFunc fout)
173 // Store away callbacks
175 MutexAutoLock autolock(g_packers_lock);
176 auto it = g_packers.find(regname);
177 if (it == g_packers.end()) {
178 auto &ref = g_packers[regname];
182 FATAL_ERROR_IF(it->second.fin != fin || it->second.fout != fout,
183 "Packer registered twice with mismatching callbacks");
187 // Save metatable so we can identify instances later
188 lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP);
189 if (lua_isnil(L, -1)) {
191 lua_pushvalue(L, -1);
192 lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP);
195 luaL_getmetatable(L, regname);
196 FATAL_ERROR_IF(lua_isnil(L, -1), "No metatable registered with that name");
198 // CUSTOM_RIDX_METATABLE_MAP contains { [metatable] = "regname", ... }
200 lua_pushstring(L, regname);
202 if (!lua_isnil(L, -1)) {
203 FATAL_ERROR_IF(lua_topointer(L, -1) != lua_topointer(L, -2),
204 "Packer registered twice with inconsistent metatable");
208 lua_pushstring(L, regname);
214 static bool find_packer(const char *regname, PackerTuple &out)
216 MutexAutoLock autolock(g_packers_lock);
217 auto it = g_packers.find(regname);
218 if (it == g_packers.end())
220 // copy data for thread safety
221 out.first = it->first;
222 out.second = it->second;
226 static bool find_packer(lua_State *L, int idx, PackerTuple &out)
229 StackChecker checker(L);
232 // retrieve metatable of the object
233 if (lua_getmetatable(L, idx) != 1)
236 // use our global table to map it to the registry name
237 lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_METATABLE_MAP);
238 assert(lua_istable(L, -1));
239 lua_pushvalue(L, -2);
241 if (lua_isnil(L, -1)) {
246 // load the associated data
247 bool found = find_packer(lua_tostring(L, -1), out);
248 FATAL_ERROR_IF(!found, "Inconsistent internal state");
254 // Packing implementation
257 static VectorRef<PackedInstr> record_object(lua_State *L, int idx, PackedValue &pv,
258 std::unordered_map<const void *, s32> &seen)
260 const void *ptr = lua_topointer(L, idx);
262 auto found = seen.find(ptr);
263 if (found == seen.end()) {
264 seen[ptr] = pv.i.size();
265 return VectorRef<PackedInstr>();
267 s32 ref = found->second;
268 assert(ref < (s32)pv.i.size());
269 // reuse the value from first time
270 auto r = emplace(pv, INSTR_PUSHREF);
272 pv.i[ref].keep_ref = true;
276 static VectorRef<PackedInstr> pack_inner(lua_State *L, int idx, int vidx, PackedValue &pv,
277 std::unordered_map<const void *, s32> &seen)
280 StackChecker checker(L);
285 switch (lua_type(L, idx)) {
288 return emplace(pv, LUA_TNIL);
290 auto r = emplace(pv, LUA_TBOOLEAN);
291 r->bdata = lua_toboolean(L, idx);
295 auto r = emplace(pv, LUA_TNUMBER);
296 r->ndata = lua_tonumber(L, idx);
300 auto r = emplace(pv, LUA_TSTRING);
302 const char *str = lua_tolstring(L, idx, &len);
304 r->sdata.assign(str, len);
308 auto r = record_object(L, idx, pv, seen);
311 break; // execution continues
313 case LUA_TFUNCTION: {
314 auto r = record_object(L, idx, pv, seen);
317 r = emplace(pv, LUA_TFUNCTION);
318 call_string_dump(L, idx);
320 const char *str = lua_tolstring(L, -1, &len);
322 r->sdata.assign(str, len);
326 case LUA_TUSERDATA: {
327 auto r = record_object(L, idx, pv, seen);
331 if (!find_packer(L, idx, ser))
332 throw LuaError("Cannot serialize unsupported userdata");
333 pv.contains_userdata = true;
334 r = emplace(pv, LUA_TUSERDATA);
335 r->sdata = ser.first;
336 r->ptrdata = ser.second.fin(L, idx);
340 std::string err = "Cannot serialize type ";
341 err += lua_typename(L, lua_type(L, idx));
347 lua_checkstack(L, 5);
349 auto rtable = emplace(pv, LUA_TTABLE);
350 const int vi_table = vidx++;
353 while (lua_next(L, idx) != 0) {
354 // key at -2, value at -1
355 const int ktype = lua_type(L, -2), vtype = lua_type(L, -1);
356 if (ktype == LUA_TNUMBER)
357 rtable->uidata1++; // narr
359 rtable->uidata2++; // nrec
361 // check if we can use a shortcut
362 if (can_set_into(ktype, vtype) && suitable_key(L, -2)) {
363 // push only the value
364 auto rval = pack_inner(L, absidx(L, -1), vidx, pv, seen);
365 rval->pop = rval->type != LUA_TTABLE;
366 // and where to put it:
367 rval->set_into = vi_table;
368 if (ktype == LUA_TSTRING)
369 rval->sdata = lua_tostring(L, -2);
371 rval->sidata1 = lua_tointeger(L, -2);
372 // pop tables after the fact
374 auto ri1 = emplace(pv, INSTR_POP);
378 // push the key and value
379 pack_inner(L, absidx(L, -2), vidx, pv, seen);
381 pack_inner(L, absidx(L, -1), vidx, pv, seen);
383 // push an instruction to set them
384 auto ri1 = emplace(pv, INSTR_SETTABLE);
385 ri1->set_into = vi_table;
386 ri1->sidata1 = vidx - 2;
387 ri1->sidata2 = vidx - 1;
395 assert(vidx == vi_table + 1);
399 PackedValue *script_pack(lua_State *L, int idx)
402 idx = absidx(L, idx);
405 std::unordered_map<const void *, s32> seen;
406 pack_inner(L, idx, 1, pv, seen);
408 return new PackedValue(std::move(pv));
412 // Unpacking implementation
415 void script_unpack(lua_State *L, PackedValue *pv)
417 lua_newtable(L); // table at index top to track ref indices -> objects
418 const int top = lua_gettop(L);
421 for (size_t packed_idx = 0; packed_idx < pv->i.size(); packed_idx++) {
422 auto &i = pv->i[packed_idx];
424 // If leaving values on stack make sure there's space (every 5th iteration)
425 if (!i.pop && (ctr++) >= 5) {
426 lua_checkstack(L, 5);
433 lua_pushvalue(L, top + i.sidata1); // key
434 lua_pushvalue(L, top + i.sidata2); // value
435 lua_rawset(L, top + i.set_into);
437 if (i.sidata1 != i.sidata2) {
438 // removing moves indices so pop higher index first
439 lua_remove(L, top + std::max(i.sidata1, i.sidata2));
440 lua_remove(L, top + std::min(i.sidata1, i.sidata2));
442 lua_remove(L, top + i.sidata1);
447 lua_remove(L, top + i.sidata1);
449 lua_remove(L, top + i.sidata2);
452 lua_pushinteger(L, i.ref);
461 lua_pushboolean(L, i.bdata);
464 lua_pushnumber(L, i.ndata);
467 lua_pushlstring(L, i.sdata.data(), i.sdata.size());
470 lua_createtable(L, i.uidata1, i.uidata2);
473 luaL_loadbuffer(L, i.sdata.data(), i.sdata.size(), nullptr);
475 case LUA_TUSERDATA: {
477 sanity_check(find_packer(i.sdata.c_str(), ser));
478 ser.second.fout(L, i.ptrdata);
479 i.ptrdata = nullptr; // ownership taken by callback
489 lua_pushinteger(L, packed_idx);
490 lua_pushvalue(L, -2);
496 lua_pushvalue(L, -1);
497 if (uses_sdata(i.type))
498 lua_rawseti(L, top + i.set_into, i.sidata1);
500 lua_setfield(L, top + i.set_into, i.sdata.c_str());
507 // as part of the unpacking process we take ownership of all userdata
508 pv->contains_userdata = false;
509 // leave exactly one value on the stack
510 lua_settop(L, top+1);
518 PackedValue::~PackedValue()
520 if (!contains_userdata)
522 for (auto &i : this->i) {
523 if (i.type == LUA_TUSERDATA && i.ptrdata) {
525 if (find_packer(i.sdata.c_str(), ser)) {
526 // tell it to deallocate object
527 ser.second.fout(nullptr, i.ptrdata);
536 // script_dump_packed
540 void script_dump_packed(const PackedValue *val)
542 printf("instruction stream: [\n");
543 for (const auto &i : val->i) {
547 printf("SETTABLE(%d, %d)", i.sidata1, i.sidata2);
550 printf(i.sidata2 ? "POP(%d, %d)" : "POP(%d)", i.sidata1, i.sidata2);
553 printf("PUSHREF(%d)", i.ref);
559 printf(i.bdata ? "true" : "false");
562 printf("%f", i.ndata);
565 printf("\"%s\"", i.sdata.c_str());
568 printf("table(%d, %d)", i.uidata1, i.uidata2);
571 printf("function(%d byte)", i.sdata.size());
574 printf("userdata %s %p", i.sdata.c_str(), i.ptrdata);
577 printf("!!UNKNOWN!!");
581 if (i.type >= 0 && uses_sdata(i.type))
582 printf(", k=%d, into=%d", i.sidata1, i.set_into);
583 else if (i.type >= 0)
584 printf(", k=\"%s\", into=%d", i.sdata.c_str(), i.set_into);
586 printf(", into=%d", i.set_into);
589 printf(", keep_ref");