mirror of
https://github.com/luau-lang/luau.git
synced 2025-05-04 10:33:46 +01:00
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>
244 lines
7 KiB
C++
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);
|
|
}
|