Clearer handling of gray lists when entering generational mode

When entering generational mode, all objects are old. So, the only
objects that need to be in a gray list are threads, which can be
assigned without barriers. Changes in anything else (e.g., weak
tables) will trigger barriers that, if needed, will add the object
to a gray list.
This commit is contained in:
Roberto Ierusalimschy 2020-08-03 13:22:57 -03:00
parent 0dc5deca1c
commit b9b554e0f6
3 changed files with 79 additions and 18 deletions

36
lgc.c
View File

@ -368,12 +368,17 @@ static int remarkupvals (global_State *g) {
} }
static void cleargraylists (global_State *g) {
g->gray = g->grayagain = NULL;
g->weak = g->allweak = g->ephemeron = NULL;
}
/* /*
** mark root set and reset all gray lists, to start a new collection ** mark root set and reset all gray lists, to start a new collection
*/ */
static void restartcollection (global_State *g) { static void restartcollection (global_State *g) {
g->gray = g->grayagain = NULL; cleargraylists(g);
g->weak = g->allweak = g->ephemeron = NULL;
markobject(g, g->mainthread); markobject(g, g->mainthread);
markvalue(g, &g->l_registry); markvalue(g, &g->l_registry);
markmt(g); markmt(g);
@ -1019,19 +1024,30 @@ static void setpause (global_State *g);
/* /*
** Sweep a list of objects, deleting dead ones and turning ** Sweep a list of objects to enter generational mode. Deletes dead
** the non dead to old (without changing their colors). ** objects and turns the non dead to old. All non-dead threads---which
** are now old---must be in a gray list. Everything else is not in a
** gray list.
**
*/ */
static void sweep2old (lua_State *L, GCObject **p) { static void sweep2old (lua_State *L, GCObject **p) {
GCObject *curr; GCObject *curr;
global_State *g = G(L);
while ((curr = *p) != NULL) { while ((curr = *p) != NULL) {
if (iswhite(curr)) { /* is 'curr' dead? */ if (iswhite(curr)) { /* is 'curr' dead? */
lua_assert(isdead(G(L), curr)); lua_assert(isdead(g, curr));
*p = curr->next; /* remove 'curr' from list */ *p = curr->next; /* remove 'curr' from list */
freeobj(L, curr); /* erase 'curr' */ freeobj(L, curr); /* erase 'curr' */
} }
else { /* all surviving objects become old */ else { /* all surviving objects become old */
setage(curr, G_OLD); setage(curr, G_OLD);
if (curr->tt == LUA_VTHREAD) { /* threads must be watched */
lua_State *th = gco2th(curr);
linkgclist(th, g->grayagain); /* insert into 'grayagain' list */
black2gray(th); /* OK if already gray */
}
else /* everything else is black */
gray2black(curr); /* OK if already black */
p = &curr->next; /* go to next element */ p = &curr->next; /* go to next element */
} }
} }
@ -1221,7 +1237,14 @@ static void youngcollection (lua_State *L, global_State *g) {
} }
/*
** Clears all gray lists, sweeps objects, and prepare sublists to enter
** generational mode. The sweeps remove dead objects and turn all
** surviving objects to old. Threads go back to 'grayagain'; everything
** else is turned black (not in any gray list).
*/
static void atomic2gen (lua_State *L, global_State *g) { static void atomic2gen (lua_State *L, global_State *g) {
cleargraylists(g);
/* sweep all elements making them old */ /* sweep all elements making them old */
g->gcstate = GCSswpallgc; g->gcstate = GCSswpallgc;
sweep2old(L, &g->allgc); sweep2old(L, &g->allgc);
@ -1244,7 +1267,8 @@ static void atomic2gen (lua_State *L, global_State *g) {
/* /*
** Enter generational mode. Must go until the end of an atomic cycle ** Enter generational mode. Must go until the end of an atomic cycle
** to ensure that all threads and weak tables are in the gray lists. ** to ensure that all objects are correctly marked and weak tables
** are cleared.
** Then, turn all objects into old and finishes the collection. ** Then, turn all objects into old and finishes the collection.
*/ */
static lu_mem entergen (lua_State *L, global_State *g) { static lu_mem entergen (lua_State *L, global_State *g) {

View File

@ -186,7 +186,8 @@ typedef union Header {
Memcontrol l_memcontrol = Memcontrol l_memcontrol =
{0UL, 0UL, 0UL, 0UL, (~0UL), {0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL}}; {0, 0UL, 0UL, 0UL, 0UL, (~0UL),
{0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL}};
static void freeblock (Memcontrol *mc, Header *block) { static void freeblock (Memcontrol *mc, Header *block) {
@ -225,6 +226,10 @@ void *debug_realloc (void *ud, void *b, size_t oldsize, size_t size) {
freeblock(mc, block); freeblock(mc, block);
return NULL; return NULL;
} }
if (mc->failnext) {
mc->failnext = 0;
return NULL; /* fake a single memory allocation error */
}
if (mc->countlimit != ~0UL && size != oldsize) { /* count limit in use? */ if (mc->countlimit != ~0UL && size != oldsize) { /* count limit in use? */
if (mc->countlimit == 0) if (mc->countlimit == 0)
return NULL; /* fake a memory allocation error */ return NULL; /* fake a memory allocation error */
@ -513,10 +518,12 @@ static void checkobject (global_State *g, GCObject *o, int maybedead,
} }
static void checkgraylist (global_State *g, GCObject *o) { static lu_mem checkgraylist (global_State *g, GCObject *o) {
int total = 0; /* count number of elements in the list */
((void)g); /* better to keep it available if we need to print an object */ ((void)g); /* better to keep it available if we need to print an object */
while (o) { while (o) {
lua_assert(isgray(o) || getage(o) == G_TOUCHED2); lua_assert(isgray(o) || getage(o) == G_TOUCHED2);
total++;
switch (o->tt) { switch (o->tt) {
case LUA_VTABLE: o = gco2t(o)->gclist; break; case LUA_VTABLE: o = gco2t(o)->gclist; break;
case LUA_VLCL: o = gco2lcl(o)->gclist; break; case LUA_VLCL: o = gco2lcl(o)->gclist; break;
@ -530,40 +537,54 @@ static void checkgraylist (global_State *g, GCObject *o) {
default: lua_assert(0); /* other objects cannot be in a gray list */ default: lua_assert(0); /* other objects cannot be in a gray list */
} }
} }
return total;
} }
/* /*
** Check objects in gray lists. ** Check objects in gray lists.
*/ */
static void checkgrays (global_State *g) { static lu_mem checkgrays (global_State *g) {
if (!keepinvariant(g)) return; int total = 0; /* count number of elements in all lists */
checkgraylist(g, g->gray); if (!keepinvariant(g)) return total;
checkgraylist(g, g->grayagain); total += checkgraylist(g, g->gray);
checkgraylist(g, g->weak); total += checkgraylist(g, g->grayagain);
checkgraylist(g, g->ephemeron); total += checkgraylist(g, g->weak);
total += checkgraylist(g, g->allweak);
total += checkgraylist(g, g->ephemeron);
return total;
} }
static void checklist (global_State *g, int maybedead, int tof, /* Increment 't' if 'o' should be in a gray list */
#define incifingray(o,t) \
if (isgray(o) || getage(o) == G_TOUCHED2) (t)++
static lu_mem checklist (global_State *g, int maybedead, int tof,
GCObject *newl, GCObject *survival, GCObject *old, GCObject *reallyold) { GCObject *newl, GCObject *survival, GCObject *old, GCObject *reallyold) {
GCObject *o; GCObject *o;
lu_mem total = 0; /* number of object that should be in gray lists */
for (o = newl; o != survival; o = o->next) { for (o = newl; o != survival; o = o->next) {
checkobject(g, o, maybedead, G_NEW); checkobject(g, o, maybedead, G_NEW);
incifingray(o, total);
lua_assert(!tof == !tofinalize(o)); lua_assert(!tof == !tofinalize(o));
} }
for (o = survival; o != old; o = o->next) { for (o = survival; o != old; o = o->next) {
checkobject(g, o, 0, G_SURVIVAL); checkobject(g, o, 0, G_SURVIVAL);
incifingray(o, total);
lua_assert(!tof == !tofinalize(o)); lua_assert(!tof == !tofinalize(o));
} }
for (o = old; o != reallyold; o = o->next) { for (o = old; o != reallyold; o = o->next) {
checkobject(g, o, 0, G_OLD1); checkobject(g, o, 0, G_OLD1);
incifingray(o, total);
lua_assert(!tof == !tofinalize(o)); lua_assert(!tof == !tofinalize(o));
} }
for (o = reallyold; o != NULL; o = o->next) { for (o = reallyold; o != NULL; o = o->next) {
checkobject(g, o, 0, G_OLD); checkobject(g, o, 0, G_OLD);
incifingray(o, total);
lua_assert(!tof == !tofinalize(o)); lua_assert(!tof == !tofinalize(o));
} }
return total;
} }
@ -571,13 +592,15 @@ int lua_checkmemory (lua_State *L) {
global_State *g = G(L); global_State *g = G(L);
GCObject *o; GCObject *o;
int maybedead; int maybedead;
lu_mem totalin; /* total of objects that are in gray lists */
lu_mem totalshould; /* total of objects that should be in gray lists */
if (keepinvariant(g)) { if (keepinvariant(g)) {
lua_assert(!iswhite(g->mainthread)); lua_assert(!iswhite(g->mainthread));
lua_assert(!iswhite(gcvalue(&g->l_registry))); lua_assert(!iswhite(gcvalue(&g->l_registry)));
} }
lua_assert(!isdead(g, gcvalue(&g->l_registry))); lua_assert(!isdead(g, gcvalue(&g->l_registry)));
lua_assert(g->sweepgc == NULL || issweepphase(g)); lua_assert(g->sweepgc == NULL || issweepphase(g));
checkgrays(g); totalin = checkgrays(g);
/* check 'fixedgc' list */ /* check 'fixedgc' list */
for (o = g->fixedgc; o != NULL; o = o->next) { for (o = g->fixedgc; o != NULL; o = o->next) {
@ -586,17 +609,22 @@ int lua_checkmemory (lua_State *L) {
/* check 'allgc' list */ /* check 'allgc' list */
maybedead = (GCSatomic < g->gcstate && g->gcstate <= GCSswpallgc); maybedead = (GCSatomic < g->gcstate && g->gcstate <= GCSswpallgc);
checklist(g, maybedead, 0, g->allgc, g->survival, g->old1, g->reallyold); totalshould = checklist(g, maybedead, 0, g->allgc,
g->survival, g->old1, g->reallyold);
/* check 'finobj' list */ /* check 'finobj' list */
checklist(g, 0, 1, g->finobj, g->finobjsur, g->finobjold1, g->finobjrold); totalshould += checklist(g, 0, 1, g->finobj,
g->finobjsur, g->finobjold1, g->finobjrold);
/* check 'tobefnz' list */ /* check 'tobefnz' list */
for (o = g->tobefnz; o != NULL; o = o->next) { for (o = g->tobefnz; o != NULL; o = o->next) {
checkobject(g, o, 0, G_NEW); checkobject(g, o, 0, G_NEW);
incifingray(o, totalshould);
lua_assert(tofinalize(o)); lua_assert(tofinalize(o));
lua_assert(o->tt == LUA_VUSERDATA || o->tt == LUA_VTABLE); lua_assert(o->tt == LUA_VUSERDATA || o->tt == LUA_VTABLE);
} }
if (keepinvariant(g))
lua_assert(totalin == totalshould);
return 0; return 0;
} }
@ -807,6 +835,13 @@ static int alloc_count (lua_State *L) {
l_memcontrol.countlimit = luaL_checkinteger(L, 1); l_memcontrol.countlimit = luaL_checkinteger(L, 1);
return 0; return 0;
} }
static int alloc_failnext (lua_State *L) {
UNUSED(L);
l_memcontrol.failnext = 1;
return 0;
}
static int settrick (lua_State *L) { static int settrick (lua_State *L) {
@ -1864,6 +1899,7 @@ static const struct luaL_Reg tests_funcs[] = {
{"makeCfunc", makeCfunc}, {"makeCfunc", makeCfunc},
{"totalmem", mem_query}, {"totalmem", mem_query},
{"alloccount", alloc_count}, {"alloccount", alloc_count},
{"allocfailnext", alloc_failnext},
{"trick", settrick}, {"trick", settrick},
{"udataval", udataval}, {"udataval", udataval},
{"unref", unref}, {"unref", unref},

View File

@ -51,6 +51,7 @@
/* memory-allocator control variables */ /* memory-allocator control variables */
typedef struct Memcontrol { typedef struct Memcontrol {
int failnext;
unsigned long numblocks; unsigned long numblocks;
unsigned long total; unsigned long total;
unsigned long maxmem; unsigned long maxmem;