mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-14 14:11:08 +00:00
Add ephemeron table support
This commit is contained in:
parent
1b20fcd43c
commit
e7bcc0d98b
2 changed files with 321 additions and 47 deletions
365
VM/src/lgc.cpp
365
VM/src/lgc.cpp
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
#define GC_SWEEPPAGESTEPCOST 16
|
#define GC_SWEEPPAGESTEPCOST 16
|
||||||
|
|
||||||
|
static constexpr int kLastElement = 0x100;
|
||||||
|
|
||||||
#define GC_INTERRUPT(state) \
|
#define GC_INTERRUPT(state) \
|
||||||
{ \
|
{ \
|
||||||
void (*interrupt)(lua_State*, int) = g->cb.interrupt; \
|
void (*interrupt)(lua_State*, int) = g->cb.interrupt; \
|
||||||
|
@ -31,17 +33,17 @@
|
||||||
|
|
||||||
#define stringmark(s) reset2bits((s)->marked, WHITE0BIT, WHITE1BIT)
|
#define stringmark(s) reset2bits((s)->marked, WHITE0BIT, WHITE1BIT)
|
||||||
|
|
||||||
#define markvalue(g, o) \
|
#define markvalue(A, g, o) \
|
||||||
{ \
|
{ \
|
||||||
checkconsistency(o); \
|
checkconsistency(o); \
|
||||||
if (iscollectable(o) && iswhite(gcvalue(o))) \
|
if (iscollectable(o) && iswhite(gcvalue(o))) \
|
||||||
reallymarkobject(g, gcvalue(o)); \
|
reallymarkobject<A>(g, gcvalue(o)); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define markobject(g, t) \
|
#define markobject(A, g, t) \
|
||||||
{ \
|
{ \
|
||||||
if (iswhite(obj2gco(t))) \
|
if (iswhite(obj2gco(t))) \
|
||||||
reallymarkobject(g, obj2gco(t)); \
|
reallymarkobject<A>(g, obj2gco(t)); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef LUAI_GCMETRICS
|
#ifdef LUAI_GCMETRICS
|
||||||
|
@ -128,9 +130,14 @@ static void removeentry(LuaNode* n)
|
||||||
setttype(gkey(n), LUA_TDEADKEY); // dead key; remove it
|
setttype(gkey(n), LUA_TDEADKEY); // dead key; remove it
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reallymarkobject(global_State* g, GCObject* o)
|
template<bool A>
|
||||||
|
static void reallymarkobject(global_State* g, GCObject* o);
|
||||||
|
|
||||||
|
template<bool A>
|
||||||
|
static void reallymarkobject0(global_State* g, GCObject* o)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(iswhite(o) && !isdead(g, o));
|
LUAU_ASSERT(iswhite(o) && !isdead(g, o));
|
||||||
|
LUAU_ASSERT(!A || !isephemeronkey(o));
|
||||||
white2gray(o);
|
white2gray(o);
|
||||||
switch (o->gch.tt)
|
switch (o->gch.tt)
|
||||||
{
|
{
|
||||||
|
@ -143,13 +150,13 @@ static void reallymarkobject(global_State* g, GCObject* o)
|
||||||
Table* mt = gco2u(o)->metatable;
|
Table* mt = gco2u(o)->metatable;
|
||||||
gray2black(o); // udata are never gray
|
gray2black(o); // udata are never gray
|
||||||
if (mt)
|
if (mt)
|
||||||
markobject(g, mt);
|
markobject(A, g, mt);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case LUA_TUPVAL:
|
case LUA_TUPVAL:
|
||||||
{
|
{
|
||||||
UpVal* uv = gco2uv(o);
|
UpVal* uv = gco2uv(o);
|
||||||
markvalue(g, uv->v);
|
markvalue(A, g, uv->v);
|
||||||
if (uv->v == &uv->u.value) // closed?
|
if (uv->v == &uv->u.value) // closed?
|
||||||
gray2black(o); // open upvalues are never black
|
gray2black(o); // open upvalues are never black
|
||||||
return;
|
return;
|
||||||
|
@ -183,6 +190,170 @@ static void reallymarkobject(global_State* g, GCObject* o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void** getephemeronlink(GCObject* o) {
|
||||||
|
// TODO: These casts are not nice and could be done better (with unions)
|
||||||
|
switch (o->gch.tt)
|
||||||
|
{
|
||||||
|
case LUA_TUSERDATA:
|
||||||
|
{
|
||||||
|
return reinterpret_cast<void**>(&gco2u(o)->metatable);
|
||||||
|
}
|
||||||
|
case LUA_TFUNCTION:
|
||||||
|
{
|
||||||
|
return reinterpret_cast<void**>(&gco2cl(o)->gclist);
|
||||||
|
}
|
||||||
|
case LUA_TTABLE:
|
||||||
|
{
|
||||||
|
return reinterpret_cast<void**>(&gco2h(o)->gclist);
|
||||||
|
}
|
||||||
|
case LUA_TTHREAD:
|
||||||
|
{
|
||||||
|
return reinterpret_cast<void**>(&gco2th(o)->gclist);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LUAU_ASSERT(0);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Link a table node from the ephemeron list to the worklist.
|
||||||
|
*
|
||||||
|
* @param o Object to restore the key.
|
||||||
|
* @param n Table node
|
||||||
|
* @param list Worklist
|
||||||
|
* @return LuaNode* Adapted worklist
|
||||||
|
*/
|
||||||
|
static LuaNode* relink(GCObject* o, LuaNode* n, LuaNode* list) {
|
||||||
|
uintptr_t intptr = reinterpret_cast<uintptr_t>(list);
|
||||||
|
ttype(gkey(n)) = gkey(n)->extra[0] & 0xF; // Restore value tt and save it in the key tt.
|
||||||
|
gkey(n)->value.gc = o; // Restore the key value, key tt needs to be restored later.
|
||||||
|
gkey(n)->extra[0] = static_cast<int>(static_cast<uint32_t>(intptr & 0xFFFFFFFF));
|
||||||
|
// Save the link to the next worklist entry in the key extra slot and value tt slot.
|
||||||
|
// Note: The value tt is saved in the key tt which can be restored from the key GCObject.
|
||||||
|
if (sizeof(uintptr_t) > 4) {
|
||||||
|
LUAU_ASSERT(sizeof(uintptr_t) <= 8);
|
||||||
|
gval(n)->tt = static_cast<int>(static_cast<uint32_t>(intptr >> 32));
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Relink the ephemeron list of a key to the worklist of the current
|
||||||
|
* ephemeron key marking to ensure that no recursion is occuring.
|
||||||
|
*
|
||||||
|
* @param o Ephemeron key, used to restore the key.
|
||||||
|
* @param first Pointer to first element, will be restored to the original value.
|
||||||
|
* @param list Worklist
|
||||||
|
* @return LuaNode* Adapted worklist
|
||||||
|
*/
|
||||||
|
static LuaNode* preparelist(GCObject* o, void** first, LuaNode* list) {
|
||||||
|
void* c = *first;
|
||||||
|
LuaNode* n;
|
||||||
|
int last;
|
||||||
|
do {
|
||||||
|
n = reinterpret_cast<LuaNode*>(c); // c points to a LuaNode
|
||||||
|
last = gkey(n)->extra[0] & kLastElement; // Is last element?
|
||||||
|
c = gkey(n)->value.p; // Pointer to next ephemeron list element
|
||||||
|
list = relink(o, n, list);
|
||||||
|
} while(!last);
|
||||||
|
// Restore original value, since in userdata we use the metatable ptr for the list ptr.
|
||||||
|
*first = c == nullptr ? nullptr : reinterpret_cast<char*>(c) - 1;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restore the table node from the worklist.
|
||||||
|
*
|
||||||
|
* @param n Node to restore to the original state
|
||||||
|
* @return LuaNode* Next node on the worklist
|
||||||
|
*/
|
||||||
|
static LuaNode* nextandrestore(LuaNode* n) {
|
||||||
|
uintptr_t intptr = static_cast<uint32_t>(gkey(n)->extra[0]);
|
||||||
|
// Rebuild ptr to next element in the worklist which is stored in the key extra value
|
||||||
|
// and the tt slot of the value.
|
||||||
|
if (sizeof(uintptr_t) > 4) {
|
||||||
|
intptr |= static_cast<uintptr_t>(static_cast<uint32_t>(gval(n)->tt)) << 32;
|
||||||
|
}
|
||||||
|
ttype(gval(n)) = ttype(gkey(n)); // Value tt is saved in the key tt.
|
||||||
|
ttype(gkey(n)) = gcvalue(gkey(n))->gch.tt; // Key tt can be restored from the key gc value.
|
||||||
|
return reinterpret_cast<LuaNode*>(intptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mark object o. Special case userdata, since the metatable can be an ephemeron key which would cause recursion.
|
||||||
|
*
|
||||||
|
* @param g Global Lua state
|
||||||
|
* @param o Object to mark
|
||||||
|
* @param n Worklist
|
||||||
|
* @return LuaNode* Adapted worklist
|
||||||
|
*/
|
||||||
|
static LuaNode* reallymarkobjecthandleephemeron(global_State* g, GCObject* o, LuaNode* n) {
|
||||||
|
if (o->gch.tt == LUA_TUSERDATA) {
|
||||||
|
// Special case userdata to avoid recursion in case metatable is an ephemeron key.
|
||||||
|
Table* mt = gco2u(o)->metatable;
|
||||||
|
white2gray(o);
|
||||||
|
gray2black(o);
|
||||||
|
if (mt) {
|
||||||
|
o = obj2gco(mt);
|
||||||
|
if (isephemeronkey(o)) {
|
||||||
|
n = preparelist(o, getephemeronlink(o), n);
|
||||||
|
mt->marked &= ~bitmask(EPHEMERONKEYBIT);
|
||||||
|
}
|
||||||
|
reallymarkobject0<true>(g, o); // Note: o is a table which will be linked to gray list, so no recursion can occure.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// All other objects will be linked to the gray list.
|
||||||
|
reallymarkobject0<true>(g, o);
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Marks the ephemeron key o. This restores all the elements of the ephemeron link and marks all values.
|
||||||
|
* This is done with a working list as values might be itself ephemeron keys and recursion might result in stack overflows.
|
||||||
|
*
|
||||||
|
* @param g Global Lua state
|
||||||
|
* @param o Ephemeron key to mark
|
||||||
|
*/
|
||||||
|
static void markephemeronkey(global_State* g, GCObject* o) {
|
||||||
|
// Move the ephemeron list to the worklist
|
||||||
|
LuaNode* list = preparelist(o, getephemeronlink(o), nullptr);
|
||||||
|
o->gch.marked &= ~bitmask(EPHEMERONKEYBIT);
|
||||||
|
list = reallymarkobjecthandleephemeron(g, o, list); // Mark the object.
|
||||||
|
while (list) {
|
||||||
|
// Iterate through the worklist
|
||||||
|
LuaNode* next = nextandrestore(list);
|
||||||
|
if (iscollectable(gval(list))) {
|
||||||
|
o = gcvalue(gval(list));
|
||||||
|
if (iswhite(o)) {
|
||||||
|
// Value is collectable and needs marking
|
||||||
|
if (isephemeronkey(o)) {
|
||||||
|
// Special case ephemeron keys
|
||||||
|
next = preparelist(o, getephemeronlink(o), next);
|
||||||
|
o->gch.marked &= ~bitmask(EPHEMERONKEYBIT);
|
||||||
|
}
|
||||||
|
next = reallymarkobjecthandleephemeron(g, o, next); // Mark the object.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool A>
|
||||||
|
static void reallymarkobject(global_State* g, GCObject* o)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(iswhite(o) && !isdead(g, o));
|
||||||
|
if (A && isephemeronkey(o)) {
|
||||||
|
// Check if the object is an ephemeron key.
|
||||||
|
// This should only be done and the case in the atomic phase.
|
||||||
|
markephemeronkey(g, o);
|
||||||
|
} else {
|
||||||
|
reallymarkobject0<A>(g, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static const char* gettablemode(global_State* g, Table* h)
|
static const char* gettablemode(global_State* g, Table* h)
|
||||||
{
|
{
|
||||||
const TValue* mode = gfasttm(g, h->metatable, TM_MODE);
|
const TValue* mode = gfasttm(g, h->metatable, TM_MODE);
|
||||||
|
@ -193,33 +364,125 @@ static const char* gettablemode(global_State* g, Table* h)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Make the key of table node n an ephemeron key if it is not already and link the table node n to the list.
|
||||||
|
* While the node is in the ephemeron list the node will look like a dead node.
|
||||||
|
* The key will be marked as LUA_TDEADKEY and the value as LUA_TNIL.
|
||||||
|
* This makes the case were the key is actually dead very cheap.
|
||||||
|
*
|
||||||
|
* @param g Global Lua state
|
||||||
|
* @param n Table node to link to the nodes key ephemeron list.
|
||||||
|
*/
|
||||||
|
static void linkephemeronkey(global_State* g, LuaNode* n) {
|
||||||
|
GCObject* o = gcvalue(gkey(n));
|
||||||
|
void** link = getephemeronlink(o);
|
||||||
|
void* ptr;
|
||||||
|
int extra = ttype(gval(n));
|
||||||
|
if (isephemeronkey(o)) {
|
||||||
|
ptr = *link;
|
||||||
|
} else {
|
||||||
|
l_setbit(o->gch.marked, EPHEMERONKEYBIT);
|
||||||
|
if (o->gch.tt == LUA_TUSERDATA) {
|
||||||
|
// We need to increate the pointer since the metatable might be as key in this table and when it is marked
|
||||||
|
// as LUA_TDEADKEY has the same pointer as in this node. This might result in problems, so ensure the pointer
|
||||||
|
// is not a pointer to a valid Lua GCObject.
|
||||||
|
ptr = reinterpret_cast<char*>(gco2u(o)->metatable) + 1;
|
||||||
|
} else {
|
||||||
|
ptr = nullptr;
|
||||||
|
}
|
||||||
|
extra |= kLastElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
gkey(n)->value.p = ptr; // Pointer to next element in the ephemeron list.
|
||||||
|
gkey(n)->extra[0] = extra; // Save tt of the value.
|
||||||
|
ttype(gkey(n)) = LUA_TDEADKEY; // Make this node a dead node.
|
||||||
|
ttype(gval(n)) = LUA_TNIL;
|
||||||
|
*link = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool A>
|
||||||
|
static int traverseephemeron(global_State* g, Table* h, const char* modev) {
|
||||||
|
int i;
|
||||||
|
int hasweakkey = 0;
|
||||||
|
i = h->sizearray;
|
||||||
|
while (i--)
|
||||||
|
markvalue(A, g, &h->array[i]);
|
||||||
|
i = sizenode(h);
|
||||||
|
while (i--)
|
||||||
|
{
|
||||||
|
LuaNode* n = gnode(h, i);
|
||||||
|
LUAU_ASSERT(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n)));
|
||||||
|
if (ttisnil(gval(n)))
|
||||||
|
removeentry(n); // remove empty entries
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(!ttisnil(gkey(n)));
|
||||||
|
if (iscollectable(gkey(n))) {
|
||||||
|
if (ttype(gkey(n)) == LUA_TSTRING) {
|
||||||
|
markvalue(A, g, gkey(n));
|
||||||
|
markvalue(A, g, gval(n));
|
||||||
|
} else if (!iswhite(gcvalue(gkey(n)))){
|
||||||
|
markvalue(A, g, gval(n));
|
||||||
|
} else if /* constexpr */ (A) {
|
||||||
|
linkephemeronkey(g, n);
|
||||||
|
} else {
|
||||||
|
hasweakkey = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
markvalue(A, g, gval(n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasweakkey) {
|
||||||
|
// Non trivial case in the propagating/marking phase.
|
||||||
|
// So ensure this object is visited again in the atomic phase.
|
||||||
|
h->gclist = g->grayagain;
|
||||||
|
g->grayagain = obj2gco(h);
|
||||||
|
} else if (strchr(modev, 's') != NULL) {
|
||||||
|
// Shrinkable ephemeron table.
|
||||||
|
h->gclist = g->weak; // link to weak list for shrinking.
|
||||||
|
g->weak = obj2gco(h);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return hasweakkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool A>
|
||||||
static int traversetable(global_State* g, Table* h)
|
static int traversetable(global_State* g, Table* h)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
int weakkey = 0;
|
int weakkey = 0;
|
||||||
int weakvalue = 0;
|
int weakvalue = 0;
|
||||||
if (h->metatable)
|
if (h->metatable)
|
||||||
markobject(g, cast_to(Table*, h->metatable));
|
markobject(A, g, cast_to(Table*, h->metatable));
|
||||||
|
|
||||||
// is there a weak mode?
|
// is there a weak mode?
|
||||||
if (const char* modev = gettablemode(g, h))
|
if (const char* modev = gettablemode(g, h))
|
||||||
{
|
{
|
||||||
weakkey = (strchr(modev, 'k') != NULL);
|
weakkey = (strchr(modev, 'k') != NULL);
|
||||||
weakvalue = (strchr(modev, 'v') != NULL);
|
weakvalue = (strchr(modev, 'v') != NULL);
|
||||||
if (weakkey || weakvalue)
|
if (weakkey)
|
||||||
|
{
|
||||||
|
if (weakvalue)
|
||||||
|
{ // is really weak?
|
||||||
|
h->gclist = g->weak; // must be cleared after GC, ...
|
||||||
|
g->weak = obj2gco(h); // ... so put in the appropriate list
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return traverseephemeron<A>(g, h, modev);
|
||||||
|
}
|
||||||
|
if (weakvalue)
|
||||||
{ // is really weak?
|
{ // is really weak?
|
||||||
h->gclist = g->weak; // must be cleared after GC, ...
|
h->gclist = g->weak; // must be cleared after GC, ...
|
||||||
g->weak = obj2gco(h); // ... so put in the appropriate list
|
g->weak = obj2gco(h); // ... so put in the appropriate list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weakkey && weakvalue)
|
|
||||||
return 1;
|
|
||||||
if (!weakvalue)
|
if (!weakvalue)
|
||||||
{
|
{
|
||||||
i = h->sizearray;
|
i = h->sizearray;
|
||||||
while (i--)
|
while (i--)
|
||||||
markvalue(g, &h->array[i]);
|
markvalue(A, g, &h->array[i]);
|
||||||
}
|
}
|
||||||
i = sizenode(h);
|
i = sizenode(h);
|
||||||
while (i--)
|
while (i--)
|
||||||
|
@ -232,9 +495,9 @@ static int traversetable(global_State* g, Table* h)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!ttisnil(gkey(n)));
|
LUAU_ASSERT(!ttisnil(gkey(n)));
|
||||||
if (!weakkey)
|
if (!weakkey)
|
||||||
markvalue(g, gkey(n));
|
markvalue(A, g, gkey(n));
|
||||||
if (!weakvalue)
|
if (!weakvalue)
|
||||||
markvalue(g, gval(n));
|
markvalue(A, g, gval(n));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return weakkey || weakvalue;
|
return weakkey || weakvalue;
|
||||||
|
@ -244,6 +507,7 @@ static int traversetable(global_State* g, Table* h)
|
||||||
** All marks are conditional because a GC may happen while the
|
** All marks are conditional because a GC may happen while the
|
||||||
** prototype is still being created
|
** prototype is still being created
|
||||||
*/
|
*/
|
||||||
|
template<bool A>
|
||||||
static void traverseproto(global_State* g, Proto* f)
|
static void traverseproto(global_State* g, Proto* f)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -252,7 +516,7 @@ static void traverseproto(global_State* g, Proto* f)
|
||||||
if (f->debugname)
|
if (f->debugname)
|
||||||
stringmark(f->debugname);
|
stringmark(f->debugname);
|
||||||
for (i = 0; i < f->sizek; i++) // mark literals
|
for (i = 0; i < f->sizek; i++) // mark literals
|
||||||
markvalue(g, &f->k[i]);
|
markvalue(A, g, &f->k[i]);
|
||||||
for (i = 0; i < f->sizeupvalues; i++)
|
for (i = 0; i < f->sizeupvalues; i++)
|
||||||
{ // mark upvalue names
|
{ // mark upvalue names
|
||||||
if (f->upvalues[i])
|
if (f->upvalues[i])
|
||||||
|
@ -261,7 +525,7 @@ static void traverseproto(global_State* g, Proto* f)
|
||||||
for (i = 0; i < f->sizep; i++)
|
for (i = 0; i < f->sizep; i++)
|
||||||
{ // mark nested protos
|
{ // mark nested protos
|
||||||
if (f->p[i])
|
if (f->p[i])
|
||||||
markobject(g, f->p[i]);
|
markobject(A, g, f->p[i]);
|
||||||
}
|
}
|
||||||
for (i = 0; i < f->sizelocvars; i++)
|
for (i = 0; i < f->sizelocvars; i++)
|
||||||
{ // mark local-variable names
|
{ // mark local-variable names
|
||||||
|
@ -270,32 +534,34 @@ static void traverseproto(global_State* g, Proto* f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<bool A>
|
||||||
static void traverseclosure(global_State* g, Closure* cl)
|
static void traverseclosure(global_State* g, Closure* cl)
|
||||||
{
|
{
|
||||||
markobject(g, cl->env);
|
markobject(A, g, cl->env);
|
||||||
if (cl->isC)
|
if (cl->isC)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < cl->nupvalues; i++) // mark its upvalues
|
for (i = 0; i < cl->nupvalues; i++) // mark its upvalues
|
||||||
markvalue(g, &cl->c.upvals[i]);
|
markvalue(A, g, &cl->c.upvals[i]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
LUAU_ASSERT(cl->nupvalues == cl->l.p->nups);
|
LUAU_ASSERT(cl->nupvalues == cl->l.p->nups);
|
||||||
markobject(g, cast_to(Proto*, cl->l.p));
|
markobject(A, g, cast_to(Proto*, cl->l.p));
|
||||||
for (i = 0; i < cl->nupvalues; i++) // mark its upvalues
|
for (i = 0; i < cl->nupvalues; i++) // mark its upvalues
|
||||||
markvalue(g, &cl->l.uprefs[i]);
|
markvalue(A, g, &cl->l.uprefs[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<bool A>
|
||||||
static void traversestack(global_State* g, lua_State* l, bool clearstack)
|
static void traversestack(global_State* g, lua_State* l, bool clearstack)
|
||||||
{
|
{
|
||||||
markobject(g, l->gt);
|
markobject(A, g, l->gt);
|
||||||
if (l->namecall)
|
if (l->namecall)
|
||||||
stringmark(l->namecall);
|
stringmark(l->namecall);
|
||||||
for (StkId o = l->stack; o < l->top; o++)
|
for (StkId o = l->stack; o < l->top; o++)
|
||||||
markvalue(g, o);
|
markvalue(A, g, o);
|
||||||
// final traversal?
|
// final traversal?
|
||||||
if (g->gcstate == GCSatomic || clearstack)
|
if (g->gcstate == GCSatomic || clearstack)
|
||||||
{
|
{
|
||||||
|
@ -309,6 +575,7 @@ static void traversestack(global_State* g, lua_State* l, bool clearstack)
|
||||||
** traverse one gray object, turning it to black.
|
** traverse one gray object, turning it to black.
|
||||||
** Returns `quantity' traversed.
|
** Returns `quantity' traversed.
|
||||||
*/
|
*/
|
||||||
|
template<bool A>
|
||||||
static size_t propagatemark(global_State* g)
|
static size_t propagatemark(global_State* g)
|
||||||
{
|
{
|
||||||
GCObject* o = g->gray;
|
GCObject* o = g->gray;
|
||||||
|
@ -320,7 +587,7 @@ static size_t propagatemark(global_State* g)
|
||||||
{
|
{
|
||||||
Table* h = gco2h(o);
|
Table* h = gco2h(o);
|
||||||
g->gray = h->gclist;
|
g->gray = h->gclist;
|
||||||
if (traversetable(g, h)) // table is weak?
|
if (traversetable<A>(g, h)) // table is weak?
|
||||||
black2gray(o); // keep it gray
|
black2gray(o); // keep it gray
|
||||||
return sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h);
|
return sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h);
|
||||||
}
|
}
|
||||||
|
@ -328,7 +595,7 @@ static size_t propagatemark(global_State* g)
|
||||||
{
|
{
|
||||||
Closure* cl = gco2cl(o);
|
Closure* cl = gco2cl(o);
|
||||||
g->gray = cl->gclist;
|
g->gray = cl->gclist;
|
||||||
traverseclosure(g, cl);
|
traverseclosure<A>(g, cl);
|
||||||
return cl->isC ? sizeCclosure(cl->nupvalues) : sizeLclosure(cl->nupvalues);
|
return cl->isC ? sizeCclosure(cl->nupvalues) : sizeLclosure(cl->nupvalues);
|
||||||
}
|
}
|
||||||
case LUA_TTHREAD:
|
case LUA_TTHREAD:
|
||||||
|
@ -343,7 +610,7 @@ static size_t propagatemark(global_State* g)
|
||||||
|
|
||||||
if (!active && g->gcstate == GCSpropagate)
|
if (!active && g->gcstate == GCSpropagate)
|
||||||
{
|
{
|
||||||
traversestack(g, th, /* clearstack= */ true);
|
traversestack<A>(g, th, /* clearstack= */ true);
|
||||||
|
|
||||||
l_setbit(th->stackstate, THREAD_SLEEPINGBIT);
|
l_setbit(th->stackstate, THREAD_SLEEPINGBIT);
|
||||||
}
|
}
|
||||||
|
@ -354,7 +621,7 @@ static size_t propagatemark(global_State* g)
|
||||||
|
|
||||||
black2gray(o);
|
black2gray(o);
|
||||||
|
|
||||||
traversestack(g, th, /* clearstack= */ false);
|
traversestack<A>(g, th, /* clearstack= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
|
return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
|
||||||
|
@ -363,7 +630,7 @@ static size_t propagatemark(global_State* g)
|
||||||
{
|
{
|
||||||
Proto* p = gco2p(o);
|
Proto* p = gco2p(o);
|
||||||
g->gray = p->gclist;
|
g->gray = p->gclist;
|
||||||
traverseproto(g, p);
|
traverseproto<A>(g, p);
|
||||||
return sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
|
return sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto*) * p->sizep + sizeof(TValue) * p->sizek + p->sizelineinfo +
|
||||||
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
|
sizeof(LocVar) * p->sizelocvars + sizeof(TString*) * p->sizeupvalues;
|
||||||
}
|
}
|
||||||
|
@ -373,12 +640,13 @@ static size_t propagatemark(global_State* g)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<bool A>
|
||||||
static size_t propagateall(global_State* g)
|
static size_t propagateall(global_State* g)
|
||||||
{
|
{
|
||||||
size_t work = 0;
|
size_t work = 0;
|
||||||
while (g->gray)
|
while (g->gray)
|
||||||
{
|
{
|
||||||
work += propagatemark(g);
|
work += propagatemark<A>(g);
|
||||||
}
|
}
|
||||||
return work;
|
return work;
|
||||||
}
|
}
|
||||||
|
@ -569,29 +837,32 @@ void luaC_freeall(lua_State* L)
|
||||||
LUAU_ASSERT(g->strbufgc == NULL);
|
LUAU_ASSERT(g->strbufgc == NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<bool A>
|
||||||
static void markmt(global_State* g)
|
static void markmt(global_State* g)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < LUA_T_COUNT; i++)
|
for (i = 0; i < LUA_T_COUNT; i++)
|
||||||
if (g->mt[i])
|
if (g->mt[i])
|
||||||
markobject(g, g->mt[i]);
|
markobject(A, g, g->mt[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark root set
|
// mark root set
|
||||||
|
template<bool A>
|
||||||
static void markroot(lua_State* L)
|
static void markroot(lua_State* L)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
g->gray = NULL;
|
g->gray = NULL;
|
||||||
g->grayagain = NULL;
|
g->grayagain = NULL;
|
||||||
g->weak = NULL;
|
g->weak = NULL;
|
||||||
markobject(g, g->mainthread);
|
markobject(A, g, g->mainthread);
|
||||||
// make global table be traversed before main stack
|
// make global table be traversed before main stack
|
||||||
markobject(g, g->mainthread->gt);
|
markobject(A, g, g->mainthread->gt);
|
||||||
markvalue(g, registry(L));
|
markvalue(A, g, registry(L));
|
||||||
markmt(g);
|
markmt<A>(g);
|
||||||
g->gcstate = GCSpropagate;
|
g->gcstate = GCSpropagate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<bool A>
|
||||||
static size_t remarkupvals(global_State* g)
|
static size_t remarkupvals(global_State* g)
|
||||||
{
|
{
|
||||||
size_t work = 0;
|
size_t work = 0;
|
||||||
|
@ -600,7 +871,7 @@ static size_t remarkupvals(global_State* g)
|
||||||
work += sizeof(UpVal);
|
work += sizeof(UpVal);
|
||||||
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
|
LUAU_ASSERT(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
|
||||||
if (isgray(obj2gco(uv)))
|
if (isgray(obj2gco(uv)))
|
||||||
markvalue(g, uv->v);
|
markvalue(A, g, uv->v);
|
||||||
}
|
}
|
||||||
return work;
|
return work;
|
||||||
}
|
}
|
||||||
|
@ -617,9 +888,9 @@ static size_t atomic(lua_State* L)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// remark occasional upvalues of (maybe) dead threads
|
// remark occasional upvalues of (maybe) dead threads
|
||||||
work += remarkupvals(g);
|
work += remarkupvals<true>(g);
|
||||||
// traverse objects caught by write barrier and by 'remarkupvals'
|
// traverse objects caught by write barrier and by 'remarkupvals'
|
||||||
work += propagateall(g);
|
work += propagateall<true>(g);
|
||||||
|
|
||||||
#ifdef LUAI_GCMETRICS
|
#ifdef LUAI_GCMETRICS
|
||||||
g->gcmetrics.currcycle.atomictimeupval += recordGcDeltaTime(currts);
|
g->gcmetrics.currcycle.atomictimeupval += recordGcDeltaTime(currts);
|
||||||
|
@ -629,9 +900,9 @@ static size_t atomic(lua_State* L)
|
||||||
g->gray = g->weak;
|
g->gray = g->weak;
|
||||||
g->weak = NULL;
|
g->weak = NULL;
|
||||||
LUAU_ASSERT(!iswhite(obj2gco(g->mainthread)));
|
LUAU_ASSERT(!iswhite(obj2gco(g->mainthread)));
|
||||||
markobject(g, L); // mark running thread
|
markobject(true, g, L); // mark running thread
|
||||||
markmt(g); // mark basic metatables (again)
|
markmt<true>(g); // mark basic metatables (again)
|
||||||
work += propagateall(g);
|
work += propagateall<true>(g);
|
||||||
|
|
||||||
#ifdef LUAI_GCMETRICS
|
#ifdef LUAI_GCMETRICS
|
||||||
g->gcmetrics.currcycle.atomictimeweak += recordGcDeltaTime(currts);
|
g->gcmetrics.currcycle.atomictimeweak += recordGcDeltaTime(currts);
|
||||||
|
@ -640,7 +911,7 @@ static size_t atomic(lua_State* L)
|
||||||
// remark gray again
|
// remark gray again
|
||||||
g->gray = g->grayagain;
|
g->gray = g->grayagain;
|
||||||
g->grayagain = NULL;
|
g->grayagain = NULL;
|
||||||
work += propagateall(g);
|
work += propagateall<true>(g);
|
||||||
|
|
||||||
#ifdef LUAI_GCMETRICS
|
#ifdef LUAI_GCMETRICS
|
||||||
g->gcmetrics.currcycle.atomictimegray += recordGcDeltaTime(currts);
|
g->gcmetrics.currcycle.atomictimegray += recordGcDeltaTime(currts);
|
||||||
|
@ -733,7 +1004,7 @@ static size_t gcstep(lua_State* L, size_t limit)
|
||||||
{
|
{
|
||||||
case GCSpause:
|
case GCSpause:
|
||||||
{
|
{
|
||||||
markroot(L); // start a new collection
|
markroot<false>(L); // start a new collection
|
||||||
LUAU_ASSERT(g->gcstate == GCSpropagate);
|
LUAU_ASSERT(g->gcstate == GCSpropagate);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -741,7 +1012,7 @@ static size_t gcstep(lua_State* L, size_t limit)
|
||||||
{
|
{
|
||||||
while (g->gray && cost < limit)
|
while (g->gray && cost < limit)
|
||||||
{
|
{
|
||||||
cost += propagatemark(g);
|
cost += propagatemark<false>(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g->gray)
|
if (!g->gray)
|
||||||
|
@ -762,7 +1033,7 @@ static size_t gcstep(lua_State* L, size_t limit)
|
||||||
{
|
{
|
||||||
while (g->gray && cost < limit)
|
while (g->gray && cost < limit)
|
||||||
{
|
{
|
||||||
cost += propagatemark(g);
|
cost += propagatemark<false>(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g->gray) // no more `gray' objects
|
if (!g->gray) // no more `gray' objects
|
||||||
|
@ -969,7 +1240,7 @@ void luaC_fullgc(lua_State* L)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// run a full collection cycle
|
// run a full collection cycle
|
||||||
markroot(L);
|
markroot<false>(L);
|
||||||
while (g->gcstate != GCSpause)
|
while (g->gcstate != GCSpause)
|
||||||
{
|
{
|
||||||
gcstep(L, SIZE_MAX);
|
gcstep(L, SIZE_MAX);
|
||||||
|
@ -1003,7 +1274,7 @@ void luaC_barrierupval(lua_State* L, GCObject* v)
|
||||||
LUAU_ASSERT(iswhite(v) && !isdead(g, v));
|
LUAU_ASSERT(iswhite(v) && !isdead(g, v));
|
||||||
|
|
||||||
if (keepinvariant(g))
|
if (keepinvariant(g))
|
||||||
reallymarkobject(g, v);
|
reallymarkobject<false>(g, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v)
|
void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v)
|
||||||
|
@ -1013,7 +1284,7 @@ void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v)
|
||||||
LUAU_ASSERT(g->gcstate != GCSpause);
|
LUAU_ASSERT(g->gcstate != GCSpause);
|
||||||
// must keep invariant?
|
// must keep invariant?
|
||||||
if (keepinvariant(g))
|
if (keepinvariant(g))
|
||||||
reallymarkobject(g, v); // restore invariant
|
reallymarkobject<false>(g, v); // restore invariant
|
||||||
else // don't mind
|
else // don't mind
|
||||||
makewhite(g, o); // mark as white just to avoid other barriers
|
makewhite(g, o); // mark as white just to avoid other barriers
|
||||||
}
|
}
|
||||||
|
@ -1027,7 +1298,7 @@ void luaC_barriertable(lua_State* L, Table* t, GCObject* v)
|
||||||
if (g->gcstate == GCSpropagateagain)
|
if (g->gcstate == GCSpropagateagain)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
|
LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
|
||||||
reallymarkobject(g, v);
|
reallymarkobject<false>(g, v);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,18 +52,21 @@
|
||||||
** bit 1 - object is white (type 1)
|
** bit 1 - object is white (type 1)
|
||||||
** bit 2 - object is black
|
** bit 2 - object is black
|
||||||
** bit 3 - object is fixed (should not be collected)
|
** bit 3 - object is fixed (should not be collected)
|
||||||
|
** bit 4 - object is an ephemeron key and needs special handling when marked from white to grey or black (only used in the atomic gc phase)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define WHITE0BIT 0
|
#define WHITE0BIT 0
|
||||||
#define WHITE1BIT 1
|
#define WHITE1BIT 1
|
||||||
#define BLACKBIT 2
|
#define BLACKBIT 2
|
||||||
#define FIXEDBIT 3
|
#define FIXEDBIT 3
|
||||||
|
#define EPHEMERONKEYBIT 4
|
||||||
#define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT)
|
#define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT)
|
||||||
|
|
||||||
#define iswhite(x) test2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT)
|
#define iswhite(x) test2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT)
|
||||||
#define isblack(x) testbit((x)->gch.marked, BLACKBIT)
|
#define isblack(x) testbit((x)->gch.marked, BLACKBIT)
|
||||||
#define isgray(x) (!testbits((x)->gch.marked, WHITEBITS | bitmask(BLACKBIT)))
|
#define isgray(x) (!testbits((x)->gch.marked, WHITEBITS | bitmask(BLACKBIT)))
|
||||||
#define isfixed(x) testbit((x)->gch.marked, FIXEDBIT)
|
#define isfixed(x) testbit((x)->gch.marked, FIXEDBIT)
|
||||||
|
#define isephemeronkey(x) testbit((x)->gch.marked, EPHEMERONKEYBIT)
|
||||||
|
|
||||||
#define otherwhite(g) (g->currentwhite ^ WHITEBITS)
|
#define otherwhite(g) (g->currentwhite ^ WHITEBITS)
|
||||||
#define isdead(g, v) (((v)->gch.marked & (WHITEBITS | bitmask(FIXEDBIT))) == (otherwhite(g) & WHITEBITS))
|
#define isdead(g, v) (((v)->gch.marked & (WHITEBITS | bitmask(FIXEDBIT))) == (otherwhite(g) & WHITEBITS))
|
||||||
|
|
Loading…
Reference in a new issue