luau/VM/src/lstate.cpp
Petri Häkkinen 2173938eb0
Add tagged lightuserdata (#1087)
This change adds support for tagged lightuserdata and optional custom
typenames for lightuserdata.

Background: Lightuserdata is an efficient representation for many kinds
of unmanaged handles and resources in a game engine. However, currently
the VM only supports one kind of lightuserdata, which makes it
problematic in practice. For example, it's not possible to distinguish
between different kinds of lightuserdata in Lua bindings, which can lead
to unsafe practices and even crashes when a wrong kind of lightuserdata
is passed to a binding function. Tagged lightuserdata work similarly to
tagged userdata, i.e. they allow checking the tag quickly using
lua_tolightuserdatatagged (or lua_lightuserdatatag).

The tag is stored in the 'extra' field of TValue so it will add no cost
to the (untagged) lightuserdata type.

Alternatives would be to use full userdata values or use bitpacking to
embed type information into lightuserdata on application level.
Unfortunately these options are not that great in practice: full
userdata have major performance implications and bitpacking fails in
cases where full 64 bits are already used (e.g. pointers or 64-bit
hashes).

Lightuserdata names are not strictly necessary but they are rather
convenient when debugging Lua code. More precise error messages and
tostring returning more specific typename are useful to have in practice
(e.g. "resource" or "entity" instead of the more generic "userdata").

Impl note: I did not add support for renaming tags in
lua_setlightuserdataname as I'm not sure if it's possible to free fixed
strings. If it's simple enough, maybe we should allow renaming (although
I can't think of a specific need for it)?

---------

Co-authored-by: Petri Häkkinen <petrih@rmd.remedy.fi>
2023-12-14 15:05:51 -08:00

244 lines
7 KiB
C++

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
#include "lstate.h"
#include "ltable.h"
#include "lstring.h"
#include "lfunc.h"
#include "lmem.h"
#include "lgc.h"
#include "ldo.h"
#include "ldebug.h"
/*
** Main thread combines a thread state and the global state
*/
typedef struct LG
{
lua_State l;
global_State g;
} LG;
static void stack_init(lua_State* L1, lua_State* L)
{
// initialize CallInfo array
L1->base_ci = luaM_newarray(L, BASIC_CI_SIZE, CallInfo, L1->memcat);
L1->ci = L1->base_ci;
L1->size_ci = BASIC_CI_SIZE;
L1->end_ci = L1->base_ci + L1->size_ci - 1;
// initialize stack array
L1->stack = luaM_newarray(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue, L1->memcat);
L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK;
TValue* stack = L1->stack;
for (int i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++)
setnilvalue(stack + i); // erase new stack
L1->top = stack;
L1->stack_last = stack + (L1->stacksize - EXTRA_STACK);
// initialize first ci
L1->ci->func = L1->top;
setnilvalue(L1->top++); // `function' entry for this `ci'
L1->base = L1->ci->base = L1->top;
L1->ci->top = L1->top + LUA_MINSTACK;
}
static void freestack(lua_State* L, lua_State* L1)
{
luaM_freearray(L, L1->base_ci, L1->size_ci, CallInfo, L1->memcat);
luaM_freearray(L, L1->stack, L1->stacksize, TValue, L1->memcat);
}
/*
** open parts that may cause memory-allocation errors
*/
static void f_luaopen(lua_State* L, void* ud)
{
global_State* g = L->global;
stack_init(L, L); // init stack
L->gt = luaH_new(L, 0, 2); // table of globals
sethvalue(L, registry(L), luaH_new(L, 0, 2)); // registry
luaS_resize(L, LUA_MINSTRTABSIZE); // initial size of string table
luaT_init(L);
luaS_fix(luaS_newliteral(L, LUA_MEMERRMSG)); // pin to make sure we can always throw this error
luaS_fix(luaS_newliteral(L, LUA_ERRERRMSG)); // pin to make sure we can always throw this error
g->GCthreshold = 4 * g->totalbytes;
}
static void preinit_state(lua_State* L, global_State* g)
{
L->global = g;
L->stack = NULL;
L->stacksize = 0;
L->gt = NULL;
L->openupval = NULL;
L->size_ci = 0;
L->nCcalls = L->baseCcalls = 0;
L->status = 0;
L->base_ci = L->ci = NULL;
L->namecall = NULL;
L->cachedslot = 0;
L->singlestep = false;
L->isactive = false;
L->activememcat = 0;
L->userdata = NULL;
}
static void close_state(lua_State* L)
{
global_State* g = L->global;
luaF_close(L, L->stack); // close all upvalues for this thread
luaC_freeall(L); // collect all objects
LUAU_ASSERT(g->strt.nuse == 0);
luaM_freearray(L, L->global->strt.hash, L->global->strt.size, TString*, 0);
freestack(L, L);
for (int i = 0; i < LUA_SIZECLASSES; i++)
{
LUAU_ASSERT(g->freepages[i] == NULL);
LUAU_ASSERT(g->freegcopages[i] == NULL);
}
LUAU_ASSERT(g->allgcopages == NULL);
LUAU_ASSERT(g->totalbytes == sizeof(LG));
LUAU_ASSERT(g->memcatbytes[0] == sizeof(LG));
for (int i = 1; i < LUA_MEMORY_CATEGORIES; i++)
LUAU_ASSERT(g->memcatbytes[i] == 0);
if (L->global->ecb.close)
L->global->ecb.close(L);
(*g->frealloc)(g->ud, L, sizeof(LG), 0);
}
lua_State* luaE_newthread(lua_State* L)
{
lua_State* L1 = luaM_newgco(L, lua_State, sizeof(lua_State), L->activememcat);
luaC_init(L, L1, LUA_TTHREAD);
preinit_state(L1, L->global);
L1->activememcat = L->activememcat; // inherit the active memory category
stack_init(L1, L); // init stack
L1->gt = L->gt; // share table of globals
L1->singlestep = L->singlestep;
LUAU_ASSERT(iswhite(obj2gco(L1)));
return L1;
}
void luaE_freethread(lua_State* L, lua_State* L1, lua_Page* page)
{
global_State* g = L->global;
if (g->cb.userthread)
g->cb.userthread(NULL, L1);
freestack(L, L1);
luaM_freegco(L, L1, sizeof(lua_State), L1->memcat, page);
}
void lua_resetthread(lua_State* L)
{
// close upvalues before clearing anything
luaF_close(L, L->stack);
// clear call frames
CallInfo* ci = L->base_ci;
ci->func = L->stack;
ci->base = ci->func + 1;
ci->top = ci->base + LUA_MINSTACK;
setnilvalue(ci->func);
L->ci = ci;
if (L->size_ci != BASIC_CI_SIZE)
luaD_reallocCI(L, BASIC_CI_SIZE);
// clear thread state
L->status = LUA_OK;
L->base = L->ci->base;
L->top = L->ci->base;
L->nCcalls = L->baseCcalls = 0;
// clear thread stack
if (L->stacksize != BASIC_STACK_SIZE + EXTRA_STACK)
luaD_reallocstack(L, BASIC_STACK_SIZE);
for (int i = 0; i < L->stacksize; i++)
setnilvalue(L->stack + i);
}
int lua_isthreadreset(lua_State* L)
{
return L->ci == L->base_ci && L->base == L->top && L->status == LUA_OK;
}
lua_State* lua_newstate(lua_Alloc f, void* ud)
{
int i;
lua_State* L;
global_State* g;
void* l = (*f)(ud, NULL, 0, sizeof(LG));
if (l == NULL)
return NULL;
L = (lua_State*)l;
g = &((LG*)L)->g;
L->tt = LUA_TTHREAD;
L->marked = g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT);
L->memcat = 0;
preinit_state(L, g);
g->frealloc = f;
g->ud = ud;
g->mainthread = L;
g->uvhead.u.open.prev = &g->uvhead;
g->uvhead.u.open.next = &g->uvhead;
g->GCthreshold = 0; // mark it as unfinished state
g->registryfree = 0;
g->errorjmp = NULL;
g->rngstate = 0;
g->ptrenckey[0] = 1;
g->ptrenckey[1] = 0;
g->ptrenckey[2] = 0;
g->ptrenckey[3] = 0;
g->strt.size = 0;
g->strt.nuse = 0;
g->strt.hash = NULL;
setnilvalue(&g->pseudotemp);
setnilvalue(registry(L));
g->gcstate = GCSpause;
g->gray = NULL;
g->grayagain = NULL;
g->weak = NULL;
g->totalbytes = sizeof(LG);
g->gcgoal = LUAI_GCGOAL;
g->gcstepmul = LUAI_GCSTEPMUL;
g->gcstepsize = LUAI_GCSTEPSIZE << 10;
for (i = 0; i < LUA_SIZECLASSES; i++)
{
g->freepages[i] = NULL;
g->freegcopages[i] = NULL;
}
g->allgcopages = NULL;
g->sweepgcopage = NULL;
for (i = 0; i < LUA_T_COUNT; i++)
g->mt[i] = NULL;
for (i = 0; i < LUA_UTAG_LIMIT; i++)
g->udatagc[i] = NULL;
for (i = 0; i < LUA_LUTAG_LIMIT; i++)
g->lightuserdataname[i] = NULL;
for (i = 0; i < LUA_MEMORY_CATEGORIES; i++)
g->memcatbytes[i] = 0;
g->memcatbytes[0] = sizeof(LG);
g->cb = lua_Callbacks();
g->ecb = lua_ExecutionCallbacks();
g->gcstats = GCStats();
#ifdef LUAI_GCMETRICS
g->gcmetrics = GCMetrics();
#endif
if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0)
{
// memory allocation error: free partial state
close_state(L);
L = NULL;
}
return L;
}
void lua_close(lua_State* L)
{
L = L->global->mainthread; // only the main thread can be closed
luaF_close(L, L->stack); // close all upvalues for this thread
close_state(L);
}