#include "lgc.h"
#include "lobject.h"
#include "lstate.h"
#include "ltable.h"
#include "lfunc.h"
#include "lstring.h"
#include "ldo.h"
#include "lmem.h"
#include "ludata.h"
#include "lbuffer.h"
#include <string.h>
#define GC_SWEEPPAGESTEPCOST 16
#define GC_INTERRUPT(state) \
{ \
void (*interrupt)(lua_State*, int) = g->cb.interrupt; \
if (LUAU_UNLIKELY(!!interrupt)) \
interrupt(L, state); \
}
#define maskmarks cast_byte(~(bitmask(BLACKBIT) | WHITEBITS))
#define makewhite(g, x) ((x)->gch.marked = cast_byte(((x)->gch.marked & maskmarks) | luaC_white(g)))
#define white2gray(x) reset2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT)
#define black2gray(x) resetbit((x)->gch.marked, BLACKBIT)
#define stringmark(s) reset2bits((s)->marked, WHITE0BIT, WHITE1BIT)
#define markvalue(g, o) \
{ \
checkconsistency(o); \
if (iscollectable(o) && iswhite(gcvalue(o))) \
reallymarkobject(g, gcvalue(o)); \
}
#define markobject(g, t) \
{ \
if (iswhite(obj2gco(t))) \
reallymarkobject(g, obj2gco(t)); \
}
#ifdef LUAI_GCMETRICS
static void recordGcStateStep(global_State* g, int startgcstate, double seconds, bool assist, size_t work)
{
switch (startgcstate)
{
case GCSpause:
if (g->gcstate == GCSpropagate)
{
g->gcmetrics.currcycle.marktime += seconds;
if (assist)
g->gcmetrics.currcycle.markassisttime += seconds;
}
break;
case GCSpropagate:
case GCSpropagateagain:
g->gcmetrics.currcycle.marktime += seconds;
g->gcmetrics.currcycle.markwork += work;
if (assist)
g->gcmetrics.currcycle.markassisttime += seconds;
break;
case GCSatomic:
g->gcmetrics.currcycle.atomictime += seconds;
break;
case GCSsweep:
g->gcmetrics.currcycle.sweeptime += seconds;
g->gcmetrics.currcycle.sweepwork += work;
if (assist)
g->gcmetrics.currcycle.sweepassisttime += seconds;
break;
default:
LUAU_ASSERT(!"Unexpected GC state");
}
if (assist)
{
g->gcmetrics.stepassisttimeacc += seconds;
g->gcmetrics.currcycle.assistwork += work;
}
else
{
g->gcmetrics.stepexplicittimeacc += seconds;
g->gcmetrics.currcycle.explicitwork += work;
}
}
static double recordGcDeltaTime(double& timer)
{
double now = lua_clock();
double delta = now - timer;
timer = now;
return delta;
}
static void startGcCycleMetrics(global_State* g)
{
g->gcmetrics.currcycle.starttimestamp = lua_clock();
g->gcmetrics.currcycle.pausetime = g->gcmetrics.currcycle.starttimestamp - g->gcmetrics.lastcycle.endtimestamp;
}
static void finishGcCycleMetrics(global_State* g)
{
g->gcmetrics.currcycle.endtimestamp = lua_clock();
g->gcmetrics.currcycle.endtotalsizebytes = g->totalbytes;
g->gcmetrics.completedcycles++;
g->gcmetrics.lastcycle = g->gcmetrics.currcycle;
g->gcmetrics.currcycle = GCCycleMetrics();
g->gcmetrics.currcycle.starttotalsizebytes = g->totalbytes;
g->gcmetrics.currcycle.heaptriggersizebytes = g->GCthreshold;
}
#endif
static void removeentry(LuaNode* n)
{
LUAU_ASSERT(ttisnil(gval(n)));
if (iscollectable(gkey(n)))
setttype(gkey(n), LUA_TDEADKEY);
}
static void reallymarkobject(global_State* g, GCObject* o)
{
LUAU_ASSERT(iswhite(o) && !isdead(g, o));
white2gray(o);
switch (o->gch.tt)
{
case LUA_TSTRING:
{
return;
}
case LUA_TUSERDATA:
{
LuaTable* mt = gco2u(o)->metatable;
gray2black(o);
if (mt)
markobject(g, mt);
return;
}
case LUA_TUPVAL:
{
UpVal* uv = gco2uv(o);
markvalue(g, uv->v);
if (!upisopen(uv))
gray2black(o);
return;
}
case LUA_TFUNCTION:
{
gco2cl(o)->gclist = g->gray;
g->gray = o;
break;
}
case LUA_TTABLE:
{
gco2h(o)->gclist = g->gray;
g->gray = o;
break;
}
case LUA_TTHREAD:
{
gco2th(o)->gclist = g->gray;
g->gray = o;
break;
}
case LUA_TBUFFER:
{
gray2black(o);
return;
}
case LUA_TPROTO:
{
gco2p(o)->gclist = g->gray;
g->gray = o;
break;
}
default:
LUAU_ASSERT(0);
}
}
static const char* gettablemode(global_State* g, LuaTable* h)
{
const TValue* mode = gfasttm(g, h->metatable, TM_MODE);
if (mode && ttisstring(mode))
return svalue(mode);
return NULL;
}
static int traversetable(global_State* g, LuaTable* h)
{
int i;
int weakkey = 0;
int weakvalue = 0;
if (h->metatable)
markobject(g, cast_to(LuaTable*, h->metatable));
if (const char* modev = gettablemode(g, h))
{
weakkey = (strchr(modev, 'k') != NULL);
weakvalue = (strchr(modev, 'v') != NULL);
if (weakkey || weakvalue)
{
h->gclist = g->weak;
g->weak = obj2gco(h);
}
}
if (weakkey && weakvalue)
return 1;
if (!weakvalue)
{
i = h->sizearray;
while (i--)
markvalue(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);
else
{
LUAU_ASSERT(!ttisnil(gkey(n)));
if (!weakkey)
markvalue(g, gkey(n));
if (!weakvalue)
markvalue(g, gval(n));
}
}
return weakkey || weakvalue;
}
static void traverseproto(global_State* g, Proto* f)
{
int i;
if (f->source)
stringmark(f->source);
if (f->debugname)
stringmark(f->debugname);
for (i = 0; i < f->sizek; i++)
markvalue(g, &f->k[i]);
for (i = 0; i < f->sizeupvalues; i++)
{
if (f->upvalues[i])
stringmark(f->upvalues[i]);
}
for (i = 0; i < f->sizep; i++)
{
if (f->p[i])
markobject(g, f->p[i]);
}
for (i = 0; i < f->sizelocvars; i++)
{
if (f->locvars[i].varname)
stringmark(f->locvars[i].varname);
}
}
static void traverseclosure(global_State* g, Closure* cl)
{
markobject(g, cl->env);
if (cl->isC)
{
int i;
for (i = 0; i < cl->nupvalues; i++)
markvalue(g, &cl->c.upvals[i]);
}
else
{
int i;
LUAU_ASSERT(cl->nupvalues == cl->l.p->nups);
markobject(g, cast_to(Proto*, cl->l.p));
for (i = 0; i < cl->nupvalues; i++)
markvalue(g, &cl->l.uprefs[i]);
}
}
static void traversestack(global_State* g, lua_State* l)
{
markobject(g, l->gt);
if (l->namecall)
stringmark(l->namecall);
for (StkId o = l->stack; o < l->top; o++)
markvalue(g, o);
for (UpVal* uv = l->openupval; uv; uv = uv->u.open.threadnext)
{
LUAU_ASSERT(upisopen(uv));
uv->markedopen = 1;
markobject(g, uv);
}
}
static void clearstack(lua_State* l)
{
StkId stack_end = l->stack + l->stacksize;
for (StkId o = l->top; o < stack_end; o++)
setnilvalue(o);
}
static void shrinkstack(lua_State* L)
{
StkId lim = L->top;
for (CallInfo* ci = L->base_ci; ci <= L->ci; ci++)
{
LUAU_ASSERT(ci->top <= L->stack_last);
if (lim < ci->top)
lim = ci->top;
}
int ci_used = cast_int(L->ci - L->base_ci);
int s_used = cast_int(lim - L->stack);
if (L->size_ci > LUAI_MAXCALLS)
return;
if (3 * size_t(ci_used) < size_t(L->size_ci) && 2 * BASIC_CI_SIZE < L->size_ci)
luaD_reallocCI(L, L->size_ci / 2);
condhardstacktests(luaD_reallocCI(L, ci_used + 1));
if (3 * size_t(s_used) < size_t(L->stacksize) && 2 * (BASIC_STACK_SIZE + EXTRA_STACK) < L->stacksize)
luaD_reallocstack(L, L->stacksize / 2, 0);
condhardstacktests(luaD_reallocstack(L, s_used, 0));
}
static void shrinkstackprotected(lua_State* L)
{
struct CallContext
{
static void run(lua_State* L, void* ud)
{
shrinkstack(L);
}
} ctx = {};
int status = luaD_rawrunprotected(L, &CallContext::run, &ctx);
LUAU_ASSERT(status == LUA_OK || status == LUA_ERRMEM);
}
static size_t propagatemark(global_State* g)
{
GCObject* o = g->gray;
LUAU_ASSERT(isgray(o));
gray2black(o);
switch (o->gch.tt)
{
case LUA_TTABLE:
{
LuaTable* h = gco2h(o);
g->gray = h->gclist;
if (traversetable(g, h))
black2gray(o);
return sizeof(LuaTable) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h);
}
case LUA_TFUNCTION:
{
Closure* cl = gco2cl(o);
g->gray = cl->gclist;
traverseclosure(g, cl);
return cl->isC ? sizeCclosure(cl->nupvalues) : sizeLclosure(cl->nupvalues);
}
case LUA_TTHREAD:
{
lua_State* th = gco2th(o);
g->gray = th->gclist;
bool active = th->isactive || th == th->global->mainthread;
traversestack(g, th);
if (active)
{
th->gclist = g->grayagain;
g->grayagain = o;
black2gray(o);
}
if (!active || g->gcstate == GCSatomic)
clearstack(th);
if (g->gcstate == GCSpropagate)
shrinkstackprotected(th);
return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci;
}
case LUA_TPROTO:
{
Proto* p = gco2p(o);
g->gray = p->gclist;
traverseproto(g, p);
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 + p->sizetypeinfo;
}
default:
LUAU_ASSERT(0);
return 0;
}
}
static size_t propagateall(global_State* g)
{
size_t work = 0;
while (g->gray)
{
work += propagatemark(g);
}
return work;
}
static int isobjcleared(GCObject* o)
{
if (o->gch.tt == LUA_TSTRING)
{
stringmark(&o->ts);
return 0;
}
return iswhite(o);
}
#define iscleared(o) (iscollectable(o) && isobjcleared(gcvalue(o)))
static void tableresizeprotected(lua_State* L, LuaTable* t, int nhsize)
{
struct CallContext
{
LuaTable* t;
int nhsize;
static void run(lua_State* L, void* ud)
{
CallContext* ctx = (CallContext*)ud;
luaH_resizehash(L, ctx->t, ctx->nhsize);
}
} ctx = {t, nhsize};
int status = luaD_rawrunprotected(L, &CallContext::run, &ctx);
LUAU_ASSERT(status == LUA_OK || status == LUA_ERRMEM);
}
static size_t cleartable(lua_State* L, GCObject* l)
{
size_t work = 0;
while (l)
{
LuaTable* h = gco2h(l);
work += sizeof(LuaTable) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h);
int i = h->sizearray;
while (i--)
{
TValue* o = &h->array[i];
if (iscleared(o))
setnilvalue(o);
}
i = sizenode(h);
int activevalues = 0;
while (i--)
{
LuaNode* n = gnode(h, i);
if (!ttisnil(gval(n)))
{
if (iscleared(gkey(n)) || iscleared(gval(n)))
{
setnilvalue(gval(n));
removeentry(n);
}
else
{
activevalues++;
}
}
}
if (const char* modev = gettablemode(L->global, h))
{
if (strchr(modev, 's'))
{
if (activevalues < sizenode(h) * 3 / 8)
tableresizeprotected(L, h, activevalues);
}
}
l = h->gclist;
}
return work;
}
static void freeobj(lua_State* L, GCObject* o, lua_Page* page)
{
switch (o->gch.tt)
{
case LUA_TPROTO:
luaF_freeproto(L, gco2p(o), page);
break;
case LUA_TFUNCTION:
luaF_freeclosure(L, gco2cl(o), page);
break;
case LUA_TUPVAL:
luaF_freeupval(L, gco2uv(o), page);
break;
case LUA_TTABLE:
luaH_free(L, gco2h(o), page);
break;
case LUA_TTHREAD:
LUAU_ASSERT(gco2th(o) != L && gco2th(o) != L->global->mainthread);
luaE_freethread(L, gco2th(o), page);
break;
case LUA_TSTRING:
luaS_free(L, gco2ts(o), page);
break;
case LUA_TUSERDATA:
luaU_freeudata(L, gco2u(o), page);
break;
case LUA_TBUFFER:
luaB_freebuffer(L, gco2buf(o), page);
break;
default:
LUAU_ASSERT(0);
}
}
static void stringresizeprotected(lua_State* L, int newsize)
{
struct CallContext
{
int newsize;
static void run(lua_State* L, void* ud)
{
CallContext* ctx = (CallContext*)ud;
luaS_resize(L, ctx->newsize);
}
} ctx = {newsize};
int status = luaD_rawrunprotected(L, &CallContext::run, &ctx);
LUAU_ASSERT(status == LUA_OK || status == LUA_ERRMEM);
}
static void shrinkbuffers(lua_State* L)
{
global_State* g = L->global;
if (g->strt.nuse < cast_to(uint32_t, g->strt.size / 4) && g->strt.size > LUA_MINSTRTABSIZE * 2)
stringresizeprotected(L, g->strt.size / 2);
}
static void shrinkbuffersfull(lua_State* L)
{
global_State* g = L->global;
int hashsize = g->strt.size;
while (g->strt.nuse < cast_to(uint32_t, hashsize / 4) && hashsize > LUA_MINSTRTABSIZE * 2)
hashsize /= 2;
if (hashsize != g->strt.size)
stringresizeprotected(L, hashsize);
}
static bool deletegco(void* context, lua_Page* page, GCObject* gco)
{
lua_State* L = (lua_State*)context;
freeobj(L, gco, page);
return true;
}
void luaC_freeall(lua_State* L)
{
global_State* g = L->global;
LUAU_ASSERT(L == g->mainthread);
luaM_visitgco(L, L, deletegco);
for (int i = 0; i < g->strt.size; i++)
LUAU_ASSERT(g->strt.hash[i] == NULL);
LUAU_ASSERT(L->global->strt.nuse == 0);
}
static void markmt(global_State* g)
{
int i;
for (i = 0; i < LUA_T_COUNT; i++)
if (g->mt[i])
markobject(g, g->mt[i]);
}
static void markroot(lua_State* L)
{
global_State* g = L->global;
g->gray = NULL;
g->grayagain = NULL;
g->weak = NULL;
markobject(g, g->mainthread);
markobject(g, g->mainthread->gt);
markvalue(g, registry(L));
markmt(g);
g->gcstate = GCSpropagate;
}
static size_t remarkupvals(global_State* g)
{
size_t work = 0;
for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead; uv = uv->u.open.next)
{
work += sizeof(UpVal);
LUAU_ASSERT(upisopen(uv));
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == uv);
LUAU_ASSERT(!isblack(obj2gco(uv)));
if (isgray(obj2gco(uv)))
markvalue(g, uv->v);
}
return work;
}
static size_t clearupvals(lua_State* L)
{
global_State* g = L->global;
size_t work = 0;
for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead;)
{
work += sizeof(UpVal);
LUAU_ASSERT(upisopen(uv));
LUAU_ASSERT(uv->u.open.next->u.open.prev == uv && uv->u.open.prev->u.open.next == uv);
LUAU_ASSERT(!isblack(obj2gco(uv)));
LUAU_ASSERT(iswhite(obj2gco(uv)) || !iscollectable(uv->v) || !iswhite(gcvalue(uv->v)));
if (uv->markedopen)
{
LUAU_ASSERT(isgray(obj2gco(uv)));
uv->markedopen = 0;
uv = uv->u.open.next;
}
else
{
UpVal* next = uv->u.open.next;
luaF_closeupval(L, uv, iswhite(obj2gco(uv)));
uv = next;
}
}
return work;
}
static size_t atomic(lua_State* L)
{
global_State* g = L->global;
LUAU_ASSERT(g->gcstate == GCSatomic);
size_t work = 0;
#ifdef LUAI_GCMETRICS
double currts = lua_clock();
#endif
work += remarkupvals(g);
work += propagateall(g);
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.atomictimeupval += recordGcDeltaTime(currts);
#endif
g->gray = g->weak;
g->weak = NULL;
LUAU_ASSERT(!iswhite(obj2gco(g->mainthread)));
markobject(g, L);
markmt(g);
work += propagateall(g);
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.atomictimeweak += recordGcDeltaTime(currts);
#endif
g->gray = g->grayagain;
g->grayagain = NULL;
work += propagateall(g);
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.atomictimegray += recordGcDeltaTime(currts);
#endif
work += cleartable(L, g->weak);
g->weak = NULL;
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.atomictimeclear += recordGcDeltaTime(currts);
#endif
work += clearupvals(L);
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.atomictimeupval += recordGcDeltaTime(currts);
#endif
g->currentwhite = cast_byte(otherwhite(g));
g->sweepgcopage = g->allgcopages;
g->gcstate = GCSsweep;
return work;
}
static int sweepgcopage(lua_State* L, lua_Page* page)
{
char* start;
char* end;
int busyBlocks;
int blockSize;
luaM_getpagewalkinfo(page, &start, &end, &busyBlocks, &blockSize);
LUAU_ASSERT(busyBlocks > 0);
global_State* g = L->global;
int deadmask = otherwhite(g);
LUAU_ASSERT(testbit(deadmask, FIXEDBIT));
int newwhite = luaC_white(g);
for (char* pos = start; pos != end; pos += blockSize)
{
GCObject* gco = (GCObject*)pos;
if (gco->gch.tt == LUA_TNIL)
continue;
if ((gco->gch.marked ^ WHITEBITS) & deadmask)
{
LUAU_ASSERT(!isdead(g, gco));
gco->gch.marked = cast_byte((gco->gch.marked & maskmarks) | newwhite);
}
else
{
LUAU_ASSERT(isdead(g, gco));
freeobj(L, gco, page);
if (--busyBlocks == 0)
return int(pos - start) / blockSize + 1;
}
}
return int(end - start) / blockSize;
}
static size_t gcstep(lua_State* L, size_t limit)
{
size_t cost = 0;
global_State* g = L->global;
switch (g->gcstate)
{
case GCSpause:
{
markroot(L);
LUAU_ASSERT(g->gcstate == GCSpropagate);
break;
}
case GCSpropagate:
{
while (g->gray && cost < limit)
{
cost += propagatemark(g);
}
if (!g->gray)
{
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.propagatework = g->gcmetrics.currcycle.explicitwork + g->gcmetrics.currcycle.assistwork;
#endif
g->gray = g->grayagain;
g->grayagain = NULL;
g->gcstate = GCSpropagateagain;
}
break;
}
case GCSpropagateagain:
{
while (g->gray && cost < limit)
{
cost += propagatemark(g);
}
if (!g->gray)
{
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.propagateagainwork =
g->gcmetrics.currcycle.explicitwork + g->gcmetrics.currcycle.assistwork - g->gcmetrics.currcycle.propagatework;
#endif
g->gcstate = GCSatomic;
}
break;
}
case GCSatomic:
{
#ifdef LUAI_GCMETRICS
g->gcmetrics.currcycle.atomicstarttimestamp = lua_clock();
g->gcmetrics.currcycle.atomicstarttotalsizebytes = g->totalbytes;
#endif
g->gcstats.atomicstarttimestamp = lua_clock();
g->gcstats.atomicstarttotalsizebytes = g->totalbytes;
cost = atomic(L);
LUAU_ASSERT(g->gcstate == GCSsweep);
break;
}
case GCSsweep:
{
while (g->sweepgcopage && cost < limit)
{
lua_Page* next = luaM_getnextpage(g->sweepgcopage);
int steps = sweepgcopage(L, g->sweepgcopage);
g->sweepgcopage = next;
cost += steps * GC_SWEEPPAGESTEPCOST;
}
if (g->sweepgcopage == NULL)
{
LUAU_ASSERT(!isdead(g, obj2gco(g->mainthread)));
makewhite(g, obj2gco(g->mainthread));
shrinkbuffers(L);
g->gcstate = GCSpause;
}
break;
}
default:
LUAU_ASSERT(!"Unexpected GC state");
}
return cost;
}
static int64_t getheaptriggererroroffset(global_State* g)
{
int32_t errorKb = int32_t((g->gcstats.atomicstarttotalsizebytes - g->gcstats.heapgoalsizebytes) / 1024);
const size_t triggertermcount = sizeof(g->gcstats.triggerterms) / sizeof(g->gcstats.triggerterms[0]);
int32_t* slot = &g->gcstats.triggerterms[g->gcstats.triggertermpos % triggertermcount];
int32_t prev = *slot;
*slot = errorKb;
g->gcstats.triggerintegral += errorKb - prev;
g->gcstats.triggertermpos++;
const double Ku = 0.9;
const double Tu = 2.5;
const double Kp = 0.45 * Ku;
const double Ti = 0.8 * Tu;
const double Ki = 0.54 * Ku / Ti;
double proportionalTerm = Kp * errorKb;
double integralTerm = Ki * g->gcstats.triggerintegral;
double totalTerm = proportionalTerm + integralTerm;
return int64_t(totalTerm * 1024);
}
static size_t getheaptrigger(global_State* g, size_t heapgoal)
{
const double durationthreshold = 1e-3;
double allocationduration = g->gcstats.atomicstarttimestamp - g->gcstats.endtimestamp;
if (allocationduration < durationthreshold)
return heapgoal;
double allocationrate = (g->gcstats.atomicstarttotalsizebytes - g->gcstats.endtotalsizebytes) / allocationduration;
double markduration = g->gcstats.atomicstarttimestamp - g->gcstats.starttimestamp;
int64_t expectedgrowth = int64_t(markduration * allocationrate);
int64_t offset = getheaptriggererroroffset(g);
int64_t heaptrigger = heapgoal - (expectedgrowth + offset);
return heaptrigger < int64_t(g->totalbytes) ? g->totalbytes : (heaptrigger > int64_t(heapgoal) ? heapgoal : size_t(heaptrigger));
}
size_t luaC_step(lua_State* L, bool assist)
{
global_State* g = L->global;
int lim = g->gcstepsize * g->gcstepmul / 100;
LUAU_ASSERT(g->totalbytes >= g->GCthreshold);
size_t debt = g->totalbytes - g->GCthreshold;
GC_INTERRUPT(0);
if (g->gcstate == GCSpause)
g->gcstats.starttimestamp = lua_clock();
#ifdef LUAI_GCMETRICS
if (g->gcstate == GCSpause)
startGcCycleMetrics(g);
double lasttimestamp = lua_clock();
#endif
int lastgcstate = g->gcstate;
size_t work = gcstep(L, lim);
#ifdef LUAI_GCMETRICS
recordGcStateStep(g, lastgcstate, lua_clock() - lasttimestamp, assist, work);
#endif
size_t actualstepsize = work * 100 / g->gcstepmul;
if (g->gcstate == GCSpause)
{
size_t heapgoal = (g->totalbytes / 100) * g->gcgoal;
size_t heaptrigger = getheaptrigger(g, heapgoal);
g->GCthreshold = heaptrigger;
g->gcstats.heapgoalsizebytes = heapgoal;
g->gcstats.endtimestamp = lua_clock();
g->gcstats.endtotalsizebytes = g->totalbytes;
#ifdef LUAI_GCMETRICS
finishGcCycleMetrics(g);
#endif
}
else
{
g->GCthreshold = g->totalbytes + actualstepsize;
if (g->GCthreshold >= debt)
g->GCthreshold -= debt;
}
GC_INTERRUPT(lastgcstate);
return actualstepsize;
}
void luaC_fullgc(lua_State* L)
{
global_State* g = L->global;
#ifdef LUAI_GCMETRICS
if (g->gcstate == GCSpause)
startGcCycleMetrics(g);
#endif
if (keepinvariant(g))
{
g->sweepgcopage = g->allgcopages;
g->gray = NULL;
g->grayagain = NULL;
g->weak = NULL;
g->gcstate = GCSsweep;
}
LUAU_ASSERT(g->gcstate == GCSpause || g->gcstate == GCSsweep);
while (g->gcstate != GCSpause)
{
LUAU_ASSERT(g->gcstate == GCSsweep);
gcstep(L, SIZE_MAX);
}
for (UpVal* uv = g->uvhead.u.open.next; uv != &g->uvhead; uv = uv->u.open.next)
{
LUAU_ASSERT(upisopen(uv));
uv->markedopen = 0;
}
#ifdef LUAI_GCMETRICS
finishGcCycleMetrics(g);
startGcCycleMetrics(g);
#endif
markroot(L);
while (g->gcstate != GCSpause)
{
gcstep(L, SIZE_MAX);
}
shrinkbuffersfull(L);
size_t heapgoalsizebytes = (g->totalbytes / 100) * g->gcgoal;
g->GCthreshold = g->totalbytes * (g->gcgoal * g->gcstepmul / 100 - 100) / g->gcstepmul;
if (g->GCthreshold < g->totalbytes)
g->GCthreshold = g->totalbytes;
g->gcstats.heapgoalsizebytes = heapgoalsizebytes;
#ifdef LUAI_GCMETRICS
finishGcCycleMetrics(g);
#endif
}
void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v)
{
global_State* g = L->global;
LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
LUAU_ASSERT(g->gcstate != GCSpause);
if (keepinvariant(g))
reallymarkobject(g, v);
else
makewhite(g, o);
}
void luaC_barriertable(lua_State* L, LuaTable* t, GCObject* v)
{
global_State* g = L->global;
GCObject* o = obj2gco(t);
if (g->gcstate == GCSpropagateagain)
{
LUAU_ASSERT(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
reallymarkobject(g, v);
return;
}
LUAU_ASSERT(isblack(o) && !isdead(g, o));
LUAU_ASSERT(g->gcstate != GCSpause);
black2gray(o);
t->gclist = g->grayagain;
g->grayagain = o;
}
void luaC_barrierback(lua_State* L, GCObject* o, GCObject** gclist)
{
global_State* g = L->global;
LUAU_ASSERT(isblack(o) && !isdead(g, o));
LUAU_ASSERT(g->gcstate != GCSpause);
black2gray(o);
*gclist = g->grayagain;
g->grayagain = o;
}
void luaC_upvalclosed(lua_State* L, UpVal* uv)
{
global_State* g = L->global;
GCObject* o = obj2gco(uv);
LUAU_ASSERT(!upisopen(uv));
if (isgray(o))
{
if (keepinvariant(g))
{
gray2black(o);
luaC_barrier(L, uv, uv->v);
}
else
{
makewhite(g, o);
LUAU_ASSERT(g->gcstate != GCSpause);
}
}
}
int64_t luaC_allocationrate(lua_State* L)
{
global_State* g = L->global;
const double durationthreshold = 1e-3;
if (g->gcstate <= GCSatomic)
{
double duration = lua_clock() - g->gcstats.endtimestamp;
if (duration < durationthreshold)
return -1;
return int64_t((g->totalbytes - g->gcstats.endtotalsizebytes) / duration);
}
double duration = g->gcstats.atomicstarttimestamp - g->gcstats.endtimestamp;
if (duration < durationthreshold)
return -1;
return int64_t((g->gcstats.atomicstarttotalsizebytes - g->gcstats.endtotalsizebytes) / duration);
}
const char* luaC_statename(int state)
{
switch (state)
{
case GCSpause:
return "pause";
case GCSpropagate:
return "mark";
case GCSpropagateagain:
return "remark";
case GCSatomic:
return "atomic";
case GCSsweep:
return "sweep";
default:
return NULL;
}
}