You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ravi/src/lgc.c

1815 lines
58 KiB

9 years ago
/*
** $Id: lgc.c $
9 years ago
** Garbage Collector
** See Copyright Notice in lua.h
*/
#define lgc_c
#define LUA_CORE
#include "lprefix.h"
6 years ago
9 years ago
#include <string.h>
9 years ago
#include "lua.h"
#include "ldebug.h"
#include "ldo.h"
#include "lfunc.h"
#include "lgc.h"
#include "lmem.h"
#include "lobject.h"
#include "lstate.h"
#include "lstring.h"
#include "ltable.h"
#include "ltm.h"
6 years ago
9 years ago
/*
** Maximum number of elements to sweep in each single step.
** (Large enough to dissipate fixed overheads but small enough
** to allow small steps for the collector.)
9 years ago
*/
#define GCSWEEPMAX 100
9 years ago
/*
** Maximum number of finalizers to call in each single step.
9 years ago
*/
#define GCFINMAX 10
9 years ago
/*
** Cost of calling one finalizer.
*/
#define GCFINALIZECOST 50
6 years ago
9 years ago
/*
** The equivalent, in bytes, of one unit of "work" (visiting a slot,
** sweeping an object, etc.)
9 years ago
*/
#define WORK2MEM sizeof(TValue)
6 years ago
9 years ago
/*
** macro to adjust 'pause': 'pause' is actually used like
** 'pause / PAUSEADJ' (value chosen by tests)
*/
6 years ago
#define PAUSEADJ 100
9 years ago
/* mask with all color bits */
#define maskcolors (bitmask(BLACKBIT) | WHITEBITS)
/* mask with all GC bits */
#define maskgcbits (maskcolors | AGEBITS)
/* macro to erase all color bits then set only the current white bit */
6 years ago
#define makewhite(g,x) \
(x->marked = cast_byte((x->marked & ~maskcolors) | luaC_white(g)))
/* make an object gray (neither white nor black) */
#define set2gray(x) resetbits(x->marked, maskcolors)
9 years ago
/* make an object black (coming from any color) */
#define set2black(x) \
(x->marked = cast_byte((x->marked & ~WHITEBITS) | bitmask(BLACKBIT)))
9 years ago
6 years ago
#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x)))
9 years ago
#define keyiswhite(n) (keyiscollectable(n) && iswhite(gckey(n)))
6 years ago
#define checkdeadkey(n) lua_assert(!ttisdeadkey(gkey(n)) || ttisnil(gval(n)))
9 years ago
/*
** Protected access to objects in values
*/
#define gcvalueN(o) (iscollectable(o) ? gcvalue(o) : NULL)
6 years ago
#define markvalue(g,o) { checkliveness(g->mainthread,o); \
6 years ago
if (valiswhite(o)) reallymarkobject(g,gcvalue(o)); }
#define markkey(g, n) { if keyiswhite(n) reallymarkobject(g,gckey(n)); }
6 years ago
#define markobject(g,t) { if (iswhite(t)) reallymarkobject(g, obj2gco(t)); }
9 years ago
/*
** mark an object that can be NULL (either because it is really optional,
** or it was stripped as debug info, or inside an uncompleted structure)
*/
6 years ago
#define markobjectN(g,t) { if (t) markobject(g,t); }
static void reallymarkobject (global_State *g, GCObject *o);
static lu_mem atomic (lua_State *L);
static void entersweep (lua_State *L);
9 years ago
/*
** {======================================================
** Generic functions
** =======================================================
*/
6 years ago
9 years ago
/*
** one after last element in a hash array
*/
#define gnodelast(h) gnode(h, cast_sizet(sizenode(h)))
static GCObject **getgclist (GCObject *o) {
switch (o->tt) {
case LUA_TTABLE: return &gco2t(o)->gclist;
case LUA_TLCL: return &gco2lcl(o)->gclist;
case LUA_TCCL: return &gco2ccl(o)->gclist;
case LUA_TTHREAD: return &gco2th(o)->gclist;
case LUA_TPROTO: return &gco2p(o)->gclist;
default: lua_assert(0); return 0;
}
}
6 years ago
9 years ago
/*
** Link a collectable object 'o' with a known type into the list 'p'.
** (Must be a macro to access the 'gclist' field in different types.)
9 years ago
*/
#define linkgclist(o,p) linkgclist_(obj2gco(o), &(o)->gclist, &(p))
static void linkgclist_ (GCObject *o, GCObject **pnext, GCObject **list) {
lua_assert(!isgray(o)); /* cannot be in a gray list */
*pnext = *list;
*list = o;
set2gray(o); /* now it is */
}
6 years ago
9 years ago
/*
** Link a generic collectable object 'o' into the list 'p'.
*/
#define linkobjgclist(o,p) linkgclist_(obj2gco(o), getgclist(o), &(p))
9 years ago
/*
9 years ago
** If key is not marked, mark its entry as dead. This allows key to be
** collected, but keeps its entry in the table. A dead node is needed
** when Lua looks up for a key (it may be part of a chain) and when
** traversing a weak table (key might be removed from the table during
** traversal). Other places never manipulate dead keys, because its
** associated nil value is enough to signal that the entry is logically
** empty.
9 years ago
*/
6 years ago
static void removeentry (Node *n) {
9 years ago
lua_assert(ttisnil(gval(n)));
if (keyiscollectable(n))
6 years ago
setdeadvalue(wgkey(n)); /* unused and unmarked key; remove it */
9 years ago
}
6 years ago
9 years ago
/*
** tells whether a key or value can be cleared from a weak
** table. Non-collectable objects are never removed from weak
** tables. Strings behave as 'values', so are never removed too. for
** other objects: if really collected, cannot keep them; for objects
** being finalized, keep them in keys, but not in values
*/
static int iscleared (global_State *g, const GCObject *o) {
if (o == NULL) return 0; /* non-collectable value */
else if (novariant(o->tt) == LUA_TSTRING) {
markobject(g, o); /* strings are 'values', so are never weak */
9 years ago
return 0;
}
else return iswhite(o);
9 years ago
}
/*
Following description is taken from:
http://wiki.luajit.org/New-Garbage-Collector#gc-algorithms_tri-color-incremental-mark-sweep
Newly allocated objects are white. The mark phase starts at the GC roots.
Marking a reachable object means flipping the color of it from white to
gray and pushing it onto a gray stack (or re-chaining it onto a gray list).
The gray stack is iteratively processed, removing one gray object at a time.
A gray object is traversed and all objects reachable from it are marked,
like above. After an object has been traversed, it's turned from gray to
black. The sweep phase works just like the two-color algorithm above.
This algorithm is incremental: the collector can operate in small steps,
processing only a couple of objects from the gray stack and then let the
mutator run again for a while. This spreads out the GC pauses into
many short intervals, which is important for highly interactive
workloads (e.g. games or internet servers).
But there's one catch: the mutator might get in the way of the collector
and store a reference to a white (unprocessed) object at a black
(processed) object. This object would never be marked and will be
freed by the sweep, even though it's clearly still referenced from a
reachable object, i.e. it should be kept alive.
To avoid this scenario, one has to preserve the tri-color invariant:
a black object may never hold a reference to a white object. This is
done with a write barrier, which has to be checked after every write.
If the invariant has been violated, a fixup step is needed.
There are two alternatives:
1. Either turn the black object gray and push it back onto the gray stack.
This is moving the barrier "back", because the object has to be reprocessed
later on. This is beneficial for container objects, because they usually
receive several stores in succession. This avoids a barrier for the next
objects that are stored into it (which are likely white, too).
2. Or immediately mark the white object, turning it gray and push it onto
the gray stack. This moves the barrier "forward", because it implicitly
drives the GC forward. This works best for objects that only receive
isolated stores. (example meta tables).
There are many optimizations to turn this into a practical algorithm.
Here are the most important:
* Stacks should always be kept gray and re-traversed just before the
final sweep phase. This avoids a write barrier for stores to stack slots,
which are the most common kind of stores.
* Objects which have no references to child objects can immediately be
turned from white to black and don't need to go through the gray stack.
* The sweep phase can be made incremental by using two whites and
flipping between them just before entering the sweep phase. Objects with
the 'current' white need to be kept. Only objects with the
'other' white should be freed.
In Lua, Tables use backward barriers, all other traversable objects
use forward barriers.
*/
9 years ago
/*
** Barrier that moves collector forward, that is, marks the white object
** 'v' being pointed by the black object 'o'. In the generational
** mode, 'v' must also become old, if 'o' is old; however, it cannot
** be changed directly to OLD, because it may still point to non-old
** objects. So, it is marked as OLD0. In the next cycle it will become
** OLD1, and in the next it will finally become OLD (regular old). By
** then, any object it points to will also be old. If called in the
** incremental sweep phase, it clears the black object to white (sweep
** it) to avoid other barrier calls for this same object. (That cannot
** be done is generational mode, as its sweep does not distinguish
** whites from deads.)
**
** Here we have a black object pointing / referencing a white object
** So to preserve tri-color invariant the white object must
** be turned gray or black. Userdata, strings and upvalues
** are turned black, whereas functions, threads, tables and protos are turned
** gray.
**
** Example: userdata o references user value v, or
** function proto o references newly added constant v
9 years ago
*/
void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) {
9 years ago
global_State *g = G(L);
lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
if (keepinvariant(g)) { /* must keep invariant? */
reallymarkobject(g, v); /* restore invariant */
if (isold(o)) {
lua_assert(!isold(v)); /* white object could not be old */
setage(v, G_OLD0); /* restore generational invariant */
}
}
else { /* sweep phase */
9 years ago
lua_assert(issweepphase(g));
if (g->gckind == KGC_INC) /* incremental mode? */
makewhite(g, o); /* mark 'o' as white to avoid other barriers */
}
}
/*
** Barrier for assignments to closed upvalues. Because upvalues are
** shared among closures, it is impossible to know the color of all
** closures pointing to it. So, we assume that the object being assigned
** must be marked.
*/
void luaC_upvalbarrier_(lua_State* L, GCObject* o) {
global_State* g = G(L);
if (keepinvariant(g)) {
markobject(g, o);
if (!isold(o)) {
setage(o, G_OLD0);
}
}
}
9 years ago
/*
** barrier that moves collector backward, that is, mark the black object
** pointing to a white object as gray again.
**
** Here we have a container (table) being assigned a value, so the
** table if black must be turned to gray as black objects cannot point
** to white objects.
9 years ago
*/
void luaC_barrierback_ (lua_State *L, GCObject *o) {
9 years ago
global_State *g = G(L);
lua_assert(isblack(o) && !isdead(g, o));
lua_assert((g->gckind == KGC_GEN) == (isold(o) && getage(o) != G_TOUCHED1));
if (getage(o) == G_TOUCHED2) /* already in gray list? */
set2gray(o); /* make it gray to become touched1 */
else /* link it in 'grayagain' and paint it gray */
linkobjgclist(o, g->grayagain);
if (isold(o)) /* generational mode? */
setage(o, G_TOUCHED1); /* touched in current cycle */
9 years ago
}
6 years ago
void luaC_fix (lua_State *L, GCObject *o) {
9 years ago
global_State *g = G(L);
6 years ago
lua_assert(g->allgc == o); /* object must be 1st in 'allgc' list! */
set2gray(o); /* they will be gray forever */
setage(o, G_OLD); /* and old forever */
6 years ago
g->allgc = o->next; /* remove object from 'allgc' list */
o->next = g->fixedgc; /* link it to 'fixedgc' list */
9 years ago
g->fixedgc = o;
}
6 years ago
9 years ago
/*
** create a new collectable object (with given type and size) and link
** it to 'allgc' list.
*/
6 years ago
GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) {
9 years ago
global_State *g = G(L);
GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz));
o->marked = luaC_white(g);
6 years ago
lua_assert(tt <= 127); /* RAVI: Must fit in a byte */
9 years ago
o->tt = tt;
o->next = g->allgc;
g->allgc = o;
return o;
}
/* }====================================================== */
6 years ago
9 years ago
/*
** {======================================================
** Mark functions
** =======================================================
*/
6 years ago
9 years ago
/*
** mark an object. Userdata, strings, and closed upvalues are visited
** and turned black here. Open upvalues are
** already indirectly linked through their respective threads in the
** 'twups' list, so they don't go to the gray list; nevertheless, they
** are kept gray to avoid barriers, as their values will be revisited
** by the thread or by 'remarkupvals'. Other objects (functions, tables,
** threads, protos) are marked gray and added to appropriate list to
** be visited (and turned black) later.
9 years ago
*/
6 years ago
static void reallymarkobject (global_State *g, GCObject *o) {
reentry:
9 years ago
switch (o->tt) {
case LUA_TSHRSTR:
9 years ago
case LUA_TLNGSTR: {
set2black(o); /* nothing to visit */
9 years ago
break;
}
case LUA_TUSERDATA: {
TValue uvalue;
6 years ago
markobjectN(g, gco2u(o)->metatable); /* mark its metatable */
set2black(o);
9 years ago
getuservalue(g->mainthread, gco2u(o), &uvalue);
if (valiswhite(&uvalue)) {
9 years ago
o = gcvalue(&uvalue);
goto reentry;
}
break;
}
/* arrays are treated like userdata. */
case RAVI_TIARRAY:
case RAVI_TFARRAY: {
RaviArray *slice = gco2array(o);
markobjectN(g, gco2array(o)->metatable); /* mark its metatable */
set2black(o);
if (slice->flags & RAVI_ARRAY_SLICE) {
TValue pvalue;
lua_assert(slice->parent);
getsliceunderlying(g->mainthread, slice, &pvalue);
if (valiswhite(&pvalue)) {
o = gcvalue(&pvalue);
goto reentry;
}
}
break;
}
case LUA_TLCL: case LUA_TCCL: case LUA_TTABLE:
case LUA_TTHREAD: case LUA_TPROTO: {
linkobjgclist(o, g->gray);
9 years ago
break;
}
default: lua_assert(0); break;
}
}
6 years ago
9 years ago
/*
** mark metamethods for basic types
*/
6 years ago
static void markmt (global_State *g) {
9 years ago
int i;
6 years ago
for (i=0; i < LUA_NUMTAGS; i++)
markobjectN(g, g->mt[i]);
9 years ago
}
6 years ago
9 years ago
/*
** mark all objects in list of being-finalized
*/
static lu_mem markbeingfnz (global_State *g) {
9 years ago
GCObject *o;
lu_mem count = 0;
for (o = g->tobefnz; o != NULL; o = o->next) {
count++;
6 years ago
markobject(g, o);
}
return count;
9 years ago
}
6 years ago
9 years ago
/*
** Mark all values stored in marked open upvalues from non-marked threads.
** (Values from marked threads were already marked when traversing the
** thread.) Remove from the list threads that no longer have upvalues and
** not-marked threads.
*/
static int remarkupvals (global_State *g) {
9 years ago
lua_State *thread;
lua_State **p = &g->twups;
int work = 0; /* estimate of how much work was done here */
9 years ago
while ((thread = *p) != NULL) {
work++;
if (!iswhite(thread) && thread->openupval != NULL)
6 years ago
p = &thread->twups; /* keep marked thread with upvalues in the list */
else { /* thread is not marked or without upvalues */
9 years ago
UpVal *uv;
lua_assert(!isold(thread) || thread->openupval == NULL);
6 years ago
*p = thread->twups; /* remove thread from the list */
thread->twups = thread; /* mark that it is out of list */
9 years ago
for (uv = thread->openupval; uv != NULL; uv = uv->u.open.next) {
work++;
9 years ago
if (uv->u.open.touched) {
6 years ago
markvalue(g, uv->v); /* remark upvalue's value */
9 years ago
uv->u.open.touched = 0;
}
}
}
}
return work;
9 years ago
}
6 years ago
static void cleargraylists (global_State *g) {
g->gray = g->grayagain = NULL;
g->weak = g->allweak = g->ephemeron = NULL;
}
9 years ago
/*
** mark root set and reset all gray lists, to start a new collection
*/
6 years ago
static void restartcollection (global_State *g) {
cleargraylists(g);
9 years ago
markobject(g, g->mainthread);
markvalue(g, &g->l_registry);
markmt(g);
6 years ago
markbeingfnz(g); /* mark any finalizing object left from previous cycle */
9 years ago
}
/* }====================================================== */
6 years ago
9 years ago
/*
** {======================================================
** Traverse functions
** =======================================================
*/
/*
** Check whether object 'o' should be kept in the 'grayagain' list for
** post-processing by 'correctgraylist'. (It could put all old objects
** in the list and leave all the work to 'correctgraylist', but it is
** more efficient to avoid adding elements that will be removed.) Only
** TOUCHED1 objects need to be in the list. TOUCHED2 doesn't need to go
** back to a gray list, but then it must become OLD. (That is what
** 'correctgraylist' does when it finds a TOUCHED2 object.)
*/
static void genlink (global_State *g, GCObject *o) {
lua_assert(isblack(o));
if (getage(o) == G_TOUCHED1) { /* touched in this cycle? */
linkobjgclist(o, g->grayagain); /* link it back in 'grayagain' */
} /* everything else do not need to be linked back */
else if (getage(o) == G_TOUCHED2)
changeage(o, G_TOUCHED2, G_OLD); /* advance age */
}
9 years ago
/*
** Traverse a table with weak values and link it to proper list. During
** propagate phase, keep it in 'grayagain' list, to be revisited in the
** atomic phase. In the atomic phase, if table has any white value,
** put it in 'weak' list, to be cleared.
*/
6 years ago
static void traverseweakvalue (global_State *g, Table *h) {
9 years ago
Node *n, *limit = gnodelast(h);
/* if there is array part, assume it may have white values (it is not
worth traversing it now just to check) */
int hasclears = (h->sizearray > 0);
6 years ago
for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */
6 years ago
if (ttisnil(gval(n))) /* entry is empty? */
removeentry(n); /* remove it */
9 years ago
else {
lua_assert(!ttisnil(gkey(n)));
6 years ago
markvalue(g, gkey(n)); /* mark key */
if (!hasclears && iscleared(g, gcvalueN(gval(n)))) /* is there a white value? */
6 years ago
hasclears = 1; /* table will have to be cleared */
9 years ago
}
}
if (g->gcstate == GCSatomic && hasclears)
6 years ago
linkgclist(h, g->weak); /* has to be cleared later */
else
linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */
9 years ago
}
6 years ago
9 years ago
/*
** Traverse an ephemeron table and link it to proper list. Returns true
** iff any object was marked during this traversal (which implies that
** convergence has to continue). During propagation phase, keep table
** in 'grayagain' list, to be visited again in the atomic phase. In
** the atomic phase, if table has any white->white entry, it has to
** be revisited during ephemeron convergence (as that key may turn
** black). Otherwise, if it has any white key, table has to be cleared
** (in the atomic phase). In generational mode, some tables
** must be kept in some gray list for post-processing; this is done
** by 'genlink'.
9 years ago
*/
static int traverseephemeron (global_State *g, Table *h, int inv) {
6 years ago
int marked = 0; /* true if an object is marked in this traversal */
int hasclears = 0; /* true if table has white keys */
int hasww = 0; /* true if table has entry "white-key -> white-value" */
9 years ago
Node *n, *limit = gnodelast(h);
unsigned int i;
/* traverse array part */
for (i = 0; i < h->sizearray; i++) {
if (valiswhite(&h->array[i])) {
marked = 1;
reallymarkobject(g, gcvalue(&h->array[i]));
}
}
/* traverse hash part */
for (n = gnode(h, 0); n < limit; n++) {
6 years ago
if (ttisnil(gval(n))) /* entry is empty? */
removeentry(n); /* remove it */
else if (iscleared(g, gcvalueN(gkey(n)))) { /* key is not marked (yet)? */
6 years ago
hasclears = 1; /* table must be cleared */
if (valiswhite(gval(n))) /* value not marked yet? */
hasww = 1; /* white-white entry */
9 years ago
}
6 years ago
else if (valiswhite(gval(n))) { /* value not marked yet? */
9 years ago
marked = 1;
6 years ago
reallymarkobject(g, gcvalue(gval(n))); /* mark it now */
9 years ago
}
}
/* link table into proper list */
if (g->gcstate == GCSpropagate)
6 years ago
linkgclist(h, g->grayagain); /* must retraverse it in atomic phase */
else if (hasww) /* table has white->white entries? */
linkgclist(h, g->ephemeron); /* have to propagate again */
else if (hasclears) /* table has white keys? */
linkgclist(h, g->allweak); /* may have to clean white keys */
else
genlink(g, obj2gco(h)); /* check whether collector still needs to see it */
9 years ago
return marked;
}
6 years ago
static void traversestrongtable (global_State *g, Table *h) {
9 years ago
Node *n, *limit = gnodelast(h);
unsigned int i;
6 years ago
for (i = 0; i < h->sizearray; i++) /* traverse array part */
9 years ago
markvalue(g, &h->array[i]);
6 years ago
for (n = gnode(h, 0); n < limit; n++) { /* traverse hash part */
if (ttisnil(gval(n))) /* entry is empty? */
removeentry(n); /* remove it */
9 years ago
else {
lua_assert(!ttisnil(gkey(n)));
6 years ago
markvalue(g, gkey(n)); /* mark key */
markvalue(g, gval(n)); /* mark value */
9 years ago
}
}
genlink(g, obj2gco(h));
9 years ago
}
6 years ago
static lu_mem traversetable (global_State *g, Table *h) {
9 years ago
const char *weakkey, *weakvalue;
const TValue *mode = gfasttm(g, h->metatable, TM_MODE);
9 years ago
markobjectN(g, h->metatable);
6 years ago
if (mode && ttisstring(mode) && /* is there a weak mode? */
(cast_void(weakkey = strchr(svalue(mode), 'k')),
cast_void(weakvalue = strchr(svalue(mode), 'v')),
6 years ago
(weakkey || weakvalue))) { /* is really weak? */
if (!weakkey) /* strong keys? */
9 years ago
traverseweakvalue(g, h);
6 years ago
else if (!weakvalue) /* strong values? */
traverseephemeron(g, h, 0);
6 years ago
else /* all weak */
linkgclist(h, g->allweak); /* nothing to traverse now */
9 years ago
}
6 years ago
else /* not weak */
9 years ago
traversestrongtable(g, h);
return 1 + h->sizearray + 2 * allocsizenode(h);
9 years ago
}
6 years ago
9 years ago
/*
** Traverse a prototype. (While a prototype is being build, its
** arrays can be larger than needed; the extra slots are filled with
** NULL, so the use of 'markobjectN')
*/
6 years ago
static int traverseproto (global_State *g, Proto *f) {
9 years ago
int i;
6 years ago
if (f->cache && iswhite(f->cache))
f->cache = NULL; /* allow cache to be collected */
9 years ago
markobjectN(g, f->source);
6 years ago
for (i = 0; i < f->sizek; i++) /* mark literals */
9 years ago
markvalue(g, &f->k[i]);
7 years ago
for (i = 0; i < f->sizeupvalues; i++) { /* mark upvalue names */
9 years ago
markobjectN(g, f->upvalues[i].name);
7 years ago
markobjectN(g, f->upvalues[i].usertype); /* RAVI change */
}
for (i = 0; i < f->sizep; i++) /* mark nested protos */
9 years ago
markobjectN(g, f->p[i]);
7 years ago
for (i = 0; i < f->sizelocvars; i++) { /* mark local-variable names */
9 years ago
markobjectN(g, f->locvars[i].varname);
7 years ago
markobjectN(g, f->locvars[i].usertype); /* RAVI change */
}
return 1 + f->sizek + f->sizeupvalues + f->sizep + f->sizelocvars;
9 years ago
}
6 years ago
static int traverseCclosure (global_State *g, CClosure *cl) {
9 years ago
int i;
6 years ago
for (i = 0; i < cl->nupvalues; i++) /* mark its upvalues */
9 years ago
markvalue(g, &cl->upvalue[i]);
return 1 + cl->nupvalues;
9 years ago
}
/*
** open upvalues point to values in a thread, so those values should
** be marked when the thread is traversed except in the atomic phase
** (because then the value cannot be changed by the thread and the
** thread may not be traversed again)
*/
static int traverseLclosure (global_State *g, LClosure *cl) {
9 years ago
int i;
6 years ago
markobjectN(g, cl->p); /* mark its prototype */
for (i = 0; i < cl->nupvalues; i++) { /* visit its upvalues */
9 years ago
UpVal *uv = cl->upvals[i];
if (uv != NULL) {
if (upisopen(uv) && g->gcstate != GCSatomic)
6 years ago
uv->u.open.touched = 1; /* can be marked in 'remarkupvals' */
9 years ago
else
markvalue(g, uv->v);
}
}
return 1 + cl->nupvalues;
9 years ago
}
6 years ago
/*
** Traverse a thread, marking the elements in the stack up to its top
** and cleaning the rest of the stack in the final traversal. That
** ensures that the entire stack have valid (non-dead) objects.
** Threads have no barriers. In gen. mode, old threads must be visited
** at every cycle, because they might point to young objects. In inc.
** mode, the thread can still be modified before the end of the cycle,
** and therefore it must be visited again in the atomic phase. To ensure
** these visits, threads must return to a gray list if they are not new
** (which can only happen in generational mode) or if the traverse is in
** the propagate phase (which can only happen in incremental mode).
*/
static int traversethread (global_State *g, lua_State *th) {
UpVal *uv;
9 years ago
StkId o = th->stack;
if (isold(th) || g->gcstate == GCSpropagate)
linkgclist(th, g->grayagain); /* insert into 'grayagain' list */
6 years ago
if (o == NULL)
return 1; /* stack not completely built yet */
lua_assert(g->gcstate == GCSatomic ||
6 years ago
th->openupval == NULL || isintwups(th));
for (; o < th->top; o++) /* mark live elements in the stack */
9 years ago
markvalue(g, o);
for (uv = th->openupval; uv != NULL; uv = uv->u.open.next)
markvalue(g, uv->v); /* open upvalues cannot be collected */
if (g->gcstate == GCSatomic) { /* final traversal? */
StkId lim = th->stack + th->stacksize; /* real end of stack */
for (; o < lim; o++) /* clear not-marked stack slice */
9 years ago
setnilvalue(o);
7 years ago
/* 'remarkupvals' may have removed thread from 'twups' list */
9 years ago
if (!isintwups(th) && th->openupval != NULL) {
6 years ago
th->twups = g->twups; /* link it back to the list */
9 years ago
g->twups = th;
}
}
else if (!g->gcemergency)
9 years ago
luaD_shrinkstack(th); /* do not change stack in emergency cycle */
return 1 + th->stacksize;
9 years ago
}
6 years ago
9 years ago
/*
** traverse one gray object, turning it to black.
9 years ago
*/
static lu_mem propagatemark (global_State *g) {
9 years ago
GCObject *o = g->gray;
nw2black(o);
g->gray = *getgclist(o); /* remove from 'gray' list */
9 years ago
switch (o->tt) {
case LUA_TTABLE: return traversetable(g, gco2t(o));
case LUA_TLCL: return traverseLclosure(g, gco2lcl(o));
case LUA_TCCL: return traverseCclosure(g, gco2ccl(o));
case LUA_TPROTO: return traverseproto(g, gco2p(o));
case LUA_TTHREAD: return traversethread(g, gco2th(o));
default: lua_assert(0); return 0;
9 years ago
}
}
6 years ago
static lu_mem propagateall (global_State *g) {
lu_mem tot = 0;
while (g->gray)
tot += propagatemark(g);
return tot;
9 years ago
}
6 years ago
/*
** Traverse all ephemeron tables propagating marks from keys to values.
** Repeat until it converges, that is, nothing new is marked. 'dir'
** inverts the direction of the traversals, trying to speed up
** convergence on chains in the same table.
**
*/
6 years ago
static void convergeephemerons (global_State *g) {
9 years ago
int changed;
int dir = 0;
9 years ago
do {
GCObject *w;
6 years ago
GCObject *next = g->ephemeron; /* get ephemeron list */
g->ephemeron = NULL; /* tables may return to this list when traversed */
9 years ago
changed = 0;
while ((w = next) != NULL) { /* for each ephemeron table */
Table *h = gco2t(w);
next = h->gclist; /* list is rebuilt during loop */
nw2black(h); /* out of the list (for now) */
if (traverseephemeron(g, h, dir)) { /* marked some value? */
6 years ago
propagateall(g); /* propagate changes */
changed = 1; /* will have to revisit all ephemeron tables */
9 years ago
}
}
dir = !dir; /* invert direction next time */
} while (changed); /* repeat until no more changes */
9 years ago
}
/* }====================================================== */
6 years ago
9 years ago
/*
** {======================================================
** Sweep Functions
** =======================================================
*/
6 years ago
9 years ago
/*
** clear entries with unmarked keys from all weaktables in list 'l'
9 years ago
*/
static void clearbykeys (global_State *g, GCObject *l) {
for (; l; l = gco2t(l)->gclist) {
9 years ago
Table *h = gco2t(l);
Node *n, *limit = gnodelast(h);
for (n = gnode(h, 0); n < limit; n++) {
if (!ttisnil(gval(n)) && (iscleared(g, gcvalueN(gkey(n))))) {
6 years ago
setnilvalue(gval(n)); /* remove value ... */
9 years ago
}
6 years ago
if (ttisnil(gval(n))) /* is entry empty? */
removeentry(n); /* remove entry from table */
9 years ago
}
}
}
6 years ago
9 years ago
/*
** clear entries with unmarked values from all weaktables in list 'l' up
** to element 'f'
*/
static void clearbyvalues (global_State *g, GCObject *l, GCObject *f) {
9 years ago
for (; l != f; l = gco2t(l)->gclist) {
Table *h = gco2t(l);
Node *n, *limit = gnodelast(h);
unsigned int i;
for (i = 0; i < h->sizearray; i++) {
TValue *o = &h->array[i];
if (iscleared(g, gcvalueN(o))) /* value was collected? */
6 years ago
setnilvalue(o); /* remove value */
9 years ago
}
for (n = gnode(h, 0); n < limit; n++) {
if (iscleared(g, gcvalueN(gval(n))))
6 years ago
setnilvalue(gval(n)); /* remove value ... */
if (ttisnil(gval(n)))
6 years ago
removeentry(n); /* and remove entry from table */
9 years ago
}
}
}
6 years ago
/*
** Decrement the reference count of an upvalue. If it goes to zero and
** upvalue is closed, delete it.
*/
6 years ago
void luaC_upvdeccount (lua_State *L, UpVal *uv) {
9 years ago
lua_assert(uv->refcount > 0);
uv->refcount--;
6 years ago
if (uv->refcount == 0 && !upisopen(uv))
luaM_free(L, uv);
9 years ago
}
6 years ago
static void freeLclosure (lua_State *L, LClosure *cl) {
9 years ago
int i;
for (i = 0; i < cl->nupvalues; i++) {
UpVal *uv = cl->upvals[i];
6 years ago
if (uv)
luaC_upvdeccount(L, uv);
9 years ago
}
luaM_freemem(L, cl, sizeLclosure(cl->nupvalues));
}
static void freeobj(lua_State *L, GCObject *o) {
9 years ago
switch (o->tt) {
case LUA_TPROTO:
luaF_freeproto(L, gco2p(o));
break;
case LUA_TLCL:
9 years ago
freeLclosure(L, gco2lcl(o));
break;
case LUA_TCCL:
9 years ago
luaM_freemem(L, o, sizeCclosure(gco2ccl(o)->nupvalues));
break;
case LUA_TTABLE:
luaH_free(L, gco2t(o));
break;
case LUA_TTHREAD:
luaE_freethread(L, gco2th(o));
break;
case LUA_TUSERDATA:
luaM_freemem(L, o, sizeudata(gco2u(o)));
break;
6 years ago
/* RAVI changes */
case RAVI_TFARRAY:
case RAVI_TIARRAY:
raviH_free(L, gco2array(o));
break;
9 years ago
case LUA_TSHRSTR:
luaS_remove(L, gco2ts(o)); /* remove it from hash table */
9 years ago
luaM_freemem(L, o, sizelstring(gco2ts(o)->shrlen));
break;
case LUA_TLNGSTR:
9 years ago
luaM_freemem(L, o, sizelstring(gco2ts(o)->u.lnglen));
9 years ago
break;
default:
lua_assert(0);
9 years ago
}
}
/*
** sweep at most 'countin' elements from a list of GCObjects erasing dead
9 years ago
** objects, where a dead object is one marked with the old (non current)
** white; change all non-dead objects back to white, preparing for next
** collection cycle. Return where to continue the traversal or NULL if
** list is finished. ('*countout' gets the number of elements traversed.)
9 years ago
*/
static GCObject **sweeplist (lua_State *L, GCObject **p, int countin,
int *countout) {
9 years ago
global_State *g = G(L);
int ow = otherwhite(g);
int i;
6 years ago
int white = luaC_white(g); /* current white */
for (i = 0; *p != NULL && i < countin; i++) {
9 years ago
GCObject *curr = *p;
int marked = curr->marked;
6 years ago
if (isdeadm(ow, marked)) { /* is 'curr' dead? */
*p = curr->next; /* remove 'curr' from list */
freeobj(L, curr); /* erase 'curr' */
9 years ago
}
6 years ago
else { /* change mark to 'white' */
curr->marked = cast_byte((marked & ~maskgcbits) | white);
6 years ago
p = &curr->next; /* go to next element */
9 years ago
}
}
if (countout)
*countout = i; /* number of elements traversed */
9 years ago
return (*p == NULL) ? NULL : p;
}
6 years ago
9 years ago
/*
** sweep a list until a live object (or end of list)
*/
6 years ago
static GCObject **sweeptolive (lua_State *L, GCObject **p) {
9 years ago
GCObject **old = p;
6 years ago
do {
p = sweeplist(L, p, 1, NULL);
6 years ago
} while (p == old);
9 years ago
return p;
}
/* }====================================================== */
6 years ago
9 years ago
/*
** {======================================================
** Finalization
** =======================================================
*/
/*
** If possible, shrink string table.
9 years ago
*/
6 years ago
static void checkSizes (lua_State *L, global_State *g) {
if (!g->gcemergency) {
if (g->strt.nuse < g->strt.size / 4) { /* string table too big? */
l_mem olddebt = g->GCdebt;
luaS_resize(L, g->strt.size / 2);
g->GCestimate += g->GCdebt - olddebt; /* correct estimate */
}
9 years ago
}
}
6 years ago
/*
** Get the next udata to be finalized from the 'tobefnz' list, and
** link it back into the 'allgc' list.
*/
6 years ago
static GCObject *udata2finalize (global_State *g) {
GCObject *o = g->tobefnz; /* get first element */
9 years ago
lua_assert(tofinalize(o));
6 years ago
g->tobefnz = o->next; /* remove it from 'tobefnz' list */
o->next = g->allgc; /* return it to 'allgc' list */
9 years ago
g->allgc = o;
6 years ago
resetbit(o->marked, FINALIZEDBIT); /* object is "normal" again */
if (issweepphase(g))
makewhite(g, o); /* "sweep" object */
else if (getage(o) == G_OLD1)
g->firstold1 = o; /* it is the first OLD1 object in the list */
9 years ago
return o;
}
6 years ago
static void dothecall (lua_State *L, void *ud) {
9 years ago
UNUSED(ud);
9 years ago
luaD_callnoyield(L, L->top - 2, 0);
9 years ago
}
6 years ago
static void GCTM (lua_State *L) {
9 years ago
global_State *g = G(L);
const TValue *tm;
TValue v;
lua_assert(!g->gcemergency);
9 years ago
setgcovalue(L, &v, udata2finalize(g));
tm = luaT_gettmbyobj(L, &v, TM_GC);
6 years ago
if (tm != NULL && ttisfunction(tm)) { /* is there a finalizer? */
9 years ago
int status;
lu_byte oldah = L->allowhook;
6 years ago
int running = g->gcrunning;
L->allowhook = 0; /* stop debug hooks during GC metamethod */
g->gcrunning = 0; /* avoid GC steps */
setobj2s(L, L->top++, tm); /* push finalizer... */
setobj2s(L, L->top++, &v); /* ... and its argument */
6 years ago
L->ci->callstatus |= CIST_FIN; /* will run a finalizer */
9 years ago
status = luaD_pcall(L, dothecall, NULL, savestack(L, L->top - 2), 0);
6 years ago
L->ci->callstatus &= ~CIST_FIN; /* not running a finalizer anymore */
L->allowhook = oldah; /* restore hooks */
g->gcrunning = running; /* restore state */
if (unlikely(status != LUA_OK)) { /* error while running __gc? */
//luaE_warnerror(L, "__gc metamethod");
L->top--; /* pops error object */
9 years ago
}
}
}
6 years ago
9 years ago
/*
** Call a few finalizers
9 years ago
*/
static int runafewfinalizers (lua_State *L, int n) {
9 years ago
global_State *g = G(L);
int i;
for (i = 0; i < n && g->tobefnz; i++)
GCTM(L); /* call one finalizer */
9 years ago
return i;
}
6 years ago
9 years ago
/*
** call all pending finalizers
*/
6 years ago
static void callallpendingfinalizers (lua_State *L) {
9 years ago
global_State *g = G(L);
6 years ago
while (g->tobefnz)
GCTM(L);
9 years ago
}
6 years ago
9 years ago
/*
** find last 'next' field in list 'p' list (to add elements in its end)
*/
6 years ago
static GCObject **findlast (GCObject **p) {
while (*p != NULL)
p = &(*p)->next;
9 years ago
return p;
}
6 years ago
9 years ago
/*
** Move all unreachable objects (or 'all' objects) that need
** finalization from list 'finobj' to list 'tobefnz' (to be finalized).
** (Note that objects after 'finobjold1' cannot be white, so they
** don't need to be traversed. In incremental mode, 'finobjold1' is NULL,
** so the whole list is traversed.)
9 years ago
*/
6 years ago
static void separatetobefnz (global_State *g, int all) {
9 years ago
GCObject *curr;
GCObject **p = &g->finobj;
GCObject **lastnext = findlast(&g->tobefnz);
while ((curr = *p) != g->finobjold1) { /* traverse all finalizable objects */
9 years ago
lua_assert(tofinalize(curr));
6 years ago
if (!(iswhite(curr) || all)) /* not being collected? */
p = &curr->next; /* don't bother with it */
9 years ago
else {
if (curr == g->finobjsur) /* removing 'finobjsur'? */
g->finobjsur = curr->next; /* correct it */
6 years ago
*p = curr->next; /* remove 'curr' from 'finobj' list */
curr->next = *lastnext; /* link at the end of 'tobefnz' list */
9 years ago
*lastnext = curr;
lastnext = &curr->next;
}
}
}
6 years ago
/*
** If pointer 'p' points to 'o', move it to the next element.
*/
static void checkpointer (GCObject **p, GCObject *o) {
if (o == *p)
*p = o->next;
}
/*
** Correct pointers to objects inside 'allgc' list when
** object 'o' is being removed from the list.
*/
static void correctpointers (global_State *g, GCObject *o) {
checkpointer(&g->survival, o);
checkpointer(&g->old1, o);
checkpointer(&g->reallyold, o);
checkpointer(&g->firstold1, o);
}
9 years ago
/*
** if object 'o' has a finalizer, remove it from 'allgc' list (must
** search the list to find it) and link it in 'finobj' list.
*/
6 years ago
void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt) {
9 years ago
global_State *g = G(L);
6 years ago
if (tofinalize(o) || /* obj. is already marked... */
gfasttm(g, mt, TM_GC) == NULL) /* or has no finalizer? */
return; /* nothing to be done */
else { /* move 'o' to 'finobj' list */
9 years ago
GCObject **p;
if (issweepphase(g)) {
6 years ago
makewhite(g, o); /* "sweep" object 'o' */
if (g->sweepgc == &o->next) /* should not remove 'sweepgc' object */
g->sweepgc = sweeptolive(L, g->sweepgc); /* change 'sweepgc' */
9 years ago
}
else
correctpointers(g, o);
9 years ago
/* search for pointer pointing to 'o' */
6 years ago
for (p = &g->allgc; *p != o; p = &(*p)->next) { /* empty */ }
*p = o->next; /* remove 'o' from 'allgc' list */
o->next = g->finobj; /* link it in 'finobj' list */
9 years ago
g->finobj = o;
6 years ago
l_setbit(o->marked, FINALIZEDBIT); /* mark it as such */
9 years ago
}
}
/* }====================================================== */
6 years ago
/*
** {======================================================
** Generational Collector
** =======================================================
*/
static void setpause (global_State *g);
/*
** Sweep a list of objects to enter generational mode. Deletes dead
** 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. Open upvalues are also kept gray.
*/
static void sweep2old (lua_State *L, GCObject **p) {
GCObject *curr;
global_State *g = G(L);
while ((curr = *p) != NULL) {
if (iswhite(curr)) { /* is 'curr' dead? */
lua_assert(isdead(g, curr));
*p = curr->next; /* remove 'curr' from list */
freeobj(L, curr); /* erase 'curr' */
}
else { /* all surviving objects become old */
setage(curr, G_OLD);
if (curr->tt == LUA_TTHREAD) { /* threads must be watched */
lua_State *th = gco2th(curr);
linkgclist(th, g->grayagain); /* insert into 'grayagain' list */
}
else /* everything else is black */
nw2black(curr);
p = &curr->next; /* go to next element */
}
}
}
/*
** Sweep for generational mode. Delete dead objects. (Because the
** collection is not incremental, there are no "new white" objects
** during the sweep. So, any white object must be dead.) For
** non-dead objects, advance their ages and clear the color of
** new objects. (Old objects keep their colors.)
** The ages of G_TOUCHED1 and G_TOUCHED2 objects cannot be advanced
** here, because these old-generation objects are usually not swept
** here. They will all be advanced in 'correctgraylist'. That function
** will also remove objects turned white here from any gray list.
*/
static GCObject **sweepgen (lua_State *L, global_State *g, GCObject **p,
GCObject *limit, GCObject **pfirstold1) {
static const lu_byte nextage[] = {
G_SURVIVAL, /* from G_NEW */
G_OLD1, /* from G_SURVIVAL */
G_OLD1, /* from G_OLD0 */
G_OLD, /* from G_OLD1 */
G_OLD, /* from G_OLD (do not change) */
G_TOUCHED1, /* from G_TOUCHED1 (do not change) */
G_TOUCHED2 /* from G_TOUCHED2 (do not change) */
};
int white = luaC_white(g);
GCObject *curr;
while ((curr = *p) != limit) {
if (iswhite(curr)) { /* is 'curr' dead? */
lua_assert(!isold(curr) && isdead(g, curr));
*p = curr->next; /* remove 'curr' from list */
freeobj(L, curr); /* erase 'curr' */
}
else { /* correct mark and age */
if (getage(curr) == G_NEW) { /* new objects go back to white */
int marked = curr->marked & ~maskgcbits; /* erase GC bits */
curr->marked = cast_byte(marked | G_SURVIVAL | white);
}
else { /* all other objects will be old, and so keep their color */
setage(curr, nextage[getage(curr)]);
if (getage(curr) == G_OLD1 && *pfirstold1 == NULL)
*pfirstold1 = curr; /* first OLD1 object in the list */
}
p = &curr->next; /* go to next element */
}
}
return p;
}
/*
** Traverse a list making all its elements white and clearing their
** age. In incremental mode, all objects are 'new' all the time,
** except for fixed strings (which are always old).
*/
static void whitelist (global_State *g, GCObject *p) {
int white = luaC_white(g);
for (; p != NULL; p = p->next)
p->marked = cast_byte((p->marked & ~maskgcbits) | white);
}
/*
** Correct a list of gray objects. Return pointer to where rest of the
** list should be linked.
** Because this correction is done after sweeping, young objects might
** be turned white and still be in the list. They are only removed.
** 'TOUCHED1' objects are advanced to 'TOUCHED2' and remain on the list;
** Non-white threads also remain on the list; 'TOUCHED2' objects become
** regular old; they and anything else are removed from the list.
*/
static GCObject **correctgraylist (GCObject **p) {
GCObject *curr;
while ((curr = *p) != NULL) {
GCObject **next = getgclist(curr);
if (iswhite(curr))
goto remove; /* remove all white objects */
else if (getage(curr) == G_TOUCHED1) { /* touched in this cycle? */
lua_assert(isgray(curr));
nw2black(curr); /* make it black, for next barrier */
changeage(curr, G_TOUCHED1, G_TOUCHED2);
goto remain; /* keep it in the list and go to next element */
}
else if (curr->tt == LUA_TTHREAD) {
lua_assert(isgray(curr));
goto remain; /* keep non-white threads on the list */
}
else { /* everything else is removed */
lua_assert(isold(curr)); /* young objects should be white here */
if (getage(curr) == G_TOUCHED2) /* advance from TOUCHED2... */
changeage(curr, G_TOUCHED2, G_OLD); /* ... to OLD */
nw2black(curr); /* make object black (to be removed) */
goto remove;
}
remove: *p = *next; continue;
remain: p = next; continue;
}
return p;
}
/*
** Correct all gray lists, coalescing them into 'grayagain'.
*/
static void correctgraylists (global_State *g) {
GCObject **list = correctgraylist(&g->grayagain);
*list = g->weak; g->weak = NULL;
list = correctgraylist(list);
*list = g->allweak; g->allweak = NULL;
list = correctgraylist(list);
*list = g->ephemeron; g->ephemeron = NULL;
correctgraylist(list);
}
/*
** Mark black 'OLD1' objects when starting a new young collection.
** Gray objects are already in some gray list, and so will be visited
** in the atomic step.
*/
static void markold (global_State *g, GCObject *from, GCObject *to) {
GCObject *p;
for (p = from; p != to; p = p->next) {
if (getage(p) == G_OLD1) {
lua_assert(!iswhite(p));
changeage(p, G_OLD1, G_OLD); /* now they are old */
if (isblack(p))
reallymarkobject(g, p);
}
}
}
/*
** Finish a young-generation collection.
*/
static void finishgencycle (lua_State *L, global_State *g) {
correctgraylists(g);
checkSizes(L, g);
g->gcstate = GCSpropagate; /* skip restart */
if (!g->gcemergency)
callallpendingfinalizers(L);
}
/*
** Does a young collection. First, mark 'OLD1' objects. Then does the
** atomic step. Then, sweep all lists and advance pointers. Finally,
** finish the collection.
*/
static void youngcollection (lua_State *L, global_State *g) {
GCObject **psurvival; /* to point to first non-dead survival object */
GCObject *dummy; /* dummy out parameter to 'sweepgen' */
lua_assert(g->gcstate == GCSpropagate);
if (g->firstold1) { /* are there regular OLD1 objects? */
markold(g, g->firstold1, g->reallyold); /* mark them */
g->firstold1 = NULL; /* no more OLD1 objects (for now) */
}
markold(g, g->finobj, g->finobjrold);
markold(g, g->tobefnz, NULL);
atomic(L);
/* sweep nursery and get a pointer to its last live element */
g->gcstate = GCSswpallgc;
psurvival = sweepgen(L, g, &g->allgc, g->survival, &g->firstold1);
/* sweep 'survival' */
sweepgen(L, g, psurvival, g->old1, &g->firstold1);
g->reallyold = g->old1;
g->old1 = *psurvival; /* 'survival' survivals are old now */
g->survival = g->allgc; /* all news are survivals */
/* repeat for 'finobj' lists */
dummy = NULL; /* no 'firstold1' optimization for 'finobj' lists */
psurvival = sweepgen(L, g, &g->finobj, g->finobjsur, &dummy);
/* sweep 'survival' */
sweepgen(L, g, psurvival, g->finobjold1, &dummy);
g->finobjrold = g->finobjold1;
g->finobjold1 = *psurvival; /* 'survival' survivals are old now */
g->finobjsur = g->finobj; /* all news are survivals */
sweepgen(L, g, &g->tobefnz, NULL, &dummy);
finishgencycle(L, 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) {
cleargraylists(g);
/* sweep all elements making them old */
g->gcstate = GCSswpallgc;
sweep2old(L, &g->allgc);
/* everything alive now is old */
g->reallyold = g->old1 = g->survival = g->allgc;
g->firstold1 = NULL; /* there are no OLD1 objects anywhere */
/* repeat for 'finobj' lists */
sweep2old(L, &g->finobj);
g->finobjrold = g->finobjold1 = g->finobjsur = g->finobj;
sweep2old(L, &g->tobefnz);
g->gckind = KGC_GEN;
g->lastatomic = 0;
g->GCestimate = gettotalbytes(g); /* base for memory control */
finishgencycle(L, g);
}
/*
** Enter generational mode. Must go until the end of an atomic cycle
** to ensure that all objects are correctly marked and weak tables
** are cleared. Then, turn all objects into old and finishes the
** collection.
*/
static lu_mem entergen (lua_State *L, global_State *g) {
lu_mem numobjs;
luaC_runtilstate(L, bitmask(GCSpause)); /* prepare to start a new cycle */
luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */
numobjs = atomic(L); /* propagates all and then do the atomic stuff */
atomic2gen(L, g);
return numobjs;
}
/*
** Enter incremental mode. Turn all objects white, make all
** intermediate lists point to NULL (to avoid invalid pointers),
** and go to the pause state.
*/
static void enterinc (global_State *g) {
whitelist(g, g->allgc);
g->reallyold = g->old1 = g->survival = NULL;
whitelist(g, g->finobj);
whitelist(g, g->tobefnz);
g->finobjrold = g->finobjold1 = g->finobjsur = NULL;
g->gcstate = GCSpause;
g->gckind = KGC_INC;
g->lastatomic = 0;
}
/*
** Change collector mode to 'newmode'.
*/
void luaC_changemode (lua_State *L, int newmode) {
global_State *g = G(L);
if (newmode != g->gckind) {
if (newmode == KGC_GEN) /* entering generational mode? */
entergen(L, g);
else
enterinc(g); /* entering incremental mode */
}
g->lastatomic = 0;
}
/*
** Does a full collection in generational mode.
*/
static lu_mem fullgen (lua_State *L, global_State *g) {
enterinc(g);
return entergen(L, g);
}
/*
** Set debt for the next minor collection, which will happen when
** memory grows 'genminormul'%.
*/
static void setminordebt (global_State *g) {
luaE_setdebt(g, -(cast(l_mem, (gettotalbytes(g) / 100)) * g->genminormul));
}
/*
** Does a major collection after last collection was a "bad collection".
**
** When the program is building a big structure, it allocates lots of
** memory but generates very little garbage. In those scenarios,
** the generational mode just wastes time doing small collections, and
** major collections are frequently what we call a "bad collection", a
** collection that frees too few objects. To avoid the cost of switching
** between generational mode and the incremental mode needed for full
** (major) collections, the collector tries to stay in incremental mode
** after a bad collection, and to switch back to generational mode only
** after a "good" collection (one that traverses less than 9/8 objects
** of the previous one).
** The collector must choose whether to stay in incremental mode or to
** switch back to generational mode before sweeping. At this point, it
** does not know the real memory in use, so it cannot use memory to
** decide whether to return to generational mode. Instead, it uses the
** number of objects traversed (returned by 'atomic') as a proxy. The
** field 'g->lastatomic' keeps this count from the last collection.
** ('g->lastatomic != 0' also means that the last collection was bad.)
*/
static void stepgenfull (lua_State *L, global_State *g) {
lu_mem newatomic; /* count of traversed objects */
lu_mem lastatomic = g->lastatomic; /* count from last collection */
if (g->gckind == KGC_GEN) /* still in generational mode? */
enterinc(g); /* enter incremental mode */
luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */
newatomic = atomic(L); /* mark everybody */
if (newatomic < lastatomic + (lastatomic >> 3)) { /* good collection? */
atomic2gen(L, g); /* return to generational mode */
setminordebt(g);
}
else { /* another bad collection; stay in incremental mode */
g->GCestimate = gettotalbytes(g); /* first estimate */;
entersweep(L);
luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */
setpause(g);
g->lastatomic = newatomic;
}
}
/*
** Does a generational "step".
** Usually, this means doing a minor collection and setting the debt to
** make another collection when memory grows 'genminormul'% larger.
**
** However, there are exceptions. If memory grows 'genmajormul'%
** larger than it was at the end of the last major collection (kept
** in 'g->GCestimate'), the function does a major collection. At the
** end, it checks whether the major collection was able to free a
** decent amount of memory (at least half the growth in memory since
** previous major collection). If so, the collector keeps its state,
** and the next collection will probably be minor again. Otherwise,
** we have what we call a "bad collection". In that case, set the field
** 'g->lastatomic' to signal that fact, so that the next collection will
** go to 'stepgenfull'.
**
** 'GCdebt <= 0' means an explicit call to GC step with "size" zero;
** in that case, do a minor collection.
*/
static void genstep (lua_State *L, global_State *g) {
if (g->lastatomic != 0) /* last collection was a bad one? */
stepgenfull(L, g); /* do a full step */
else {
lu_mem majorbase = g->GCestimate; /* memory after last major collection */
lu_mem majorinc = (majorbase / 100) * getgcparam(g->genmajormul);
if (g->GCdebt > 0 && gettotalbytes(g) > majorbase + majorinc) {
lu_mem numobjs = fullgen(L, g); /* do a major collection */
if (gettotalbytes(g) < majorbase + (majorinc / 2)) {
/* collected at least half of memory growth since last major
collection; keep doing minor collections */
setminordebt(g);
}
else { /* bad collection */
g->lastatomic = numobjs; /* signal that last collection was bad */
setpause(g); /* do a long wait for next (major) collection */
}
}
else { /* regular case; do a minor collection */
youngcollection(L, g);
setminordebt(g);
g->GCestimate = majorbase; /* preserve base value */
}
}
lua_assert(isdecGCmodegen(g));
}
/* }====================================================== */
6 years ago
9 years ago
/*
** {======================================================
** GC control
** =======================================================
*/
6 years ago
9 years ago
/*
** Set the "time" to wait before starting a new GC cycle; cycle will
** start when memory use hits the threshold of ('estimate' * pause /
** PAUSEADJ). (Division by 'estimate' should be OK: it cannot be zero,
** because Lua cannot even start with less than PAUSEADJ bytes).
9 years ago
*/
6 years ago
static void setpause (global_State *g) {
9 years ago
l_mem threshold, debt;
int pause = getgcparam(g->gcpause);
6 years ago
l_mem estimate = g->GCestimate / PAUSEADJ; /* adjust 'estimate' */
9 years ago
lua_assert(estimate > 0);
threshold = (pause < MAX_LMEM / estimate) /* overflow? */
? estimate * pause /* no overflow */
6 years ago
: MAX_LMEM; /* overflow; truncate to maximum */
9 years ago
debt = gettotalbytes(g) - threshold;
if (debt > 0) debt = 0;
9 years ago
luaE_setdebt(g, debt);
}
6 years ago
9 years ago
/*
** Enter first sweep phase.
** The call to 'sweeptolive' makes the pointer point to an object
** inside the list (instead of to the header), so that the real sweep do
** not need to skip objects created between "now" and the start of the
** real sweep.
9 years ago
*/
6 years ago
static void entersweep (lua_State *L) {
9 years ago
global_State *g = G(L);
g->gcstate = GCSswpallgc;
lua_assert(g->sweepgc == NULL);
g->sweepgc = sweeptolive(L, &g->allgc);
}
/*
** Delete all objects in list 'p' until (but not including) object
** 'limit'.
*/
static void deletelist (lua_State *L, GCObject *p, GCObject *limit) {
while (p != limit) {
GCObject *next = p->next;
freeobj(L, p);
p = next;
}
9 years ago
}
6 years ago
/*
** Call all finalizers of the objects in the given Lua state, and
** then free all objects, except for the main thread.
*/
6 years ago
void luaC_freeallobjects (lua_State *L) {
9 years ago
global_State *g = G(L);
luaC_changemode(L, KGC_INC);
6 years ago
separatetobefnz(g, 1); /* separate all objects with finalizers */
9 years ago
lua_assert(g->finobj == NULL);
callallpendingfinalizers(L);
deletelist(L, g->allgc, obj2gco(g->mainthread));
deletelist(L, g->finobj, NULL);
deletelist(L, g->fixedgc, NULL); /* collect fixed objects */
9 years ago
lua_assert(g->strt.nuse == 0);
}
6 years ago
static lu_mem atomic (lua_State *L) {
9 years ago
global_State *g = G(L);
lu_mem work = 0;
9 years ago
GCObject *origweak, *origall;
6 years ago
GCObject *grayagain = g->grayagain; /* save original list */
g->grayagain = NULL;
9 years ago
lua_assert(g->ephemeron == NULL && g->weak == NULL);
lua_assert(!iswhite(g->mainthread));
g->gcstate = GCSatomic;
6 years ago
markobject(g, L); /* mark running thread */
9 years ago
/* registry and global metatables may be changed by API */
markvalue(g, &g->l_registry);
6 years ago
markmt(g); /* mark global metatables */
work += propagateall(g); /* empties 'gray' list */
9 years ago
/* remark occasional upvalues of (maybe) dead threads */
work += remarkupvals(g);
work += propagateall(g); /* propagate changes */
9 years ago
g->gray = grayagain;
work += propagateall(g); /* traverse 'grayagain' list */
9 years ago
convergeephemerons(g);
/* at this point, all strongly accessible objects are marked. */
/* Clear values from weak tables, before checking finalizers */
clearbyvalues(g, g->weak, NULL);
clearbyvalues(g, g->allweak, NULL);
6 years ago
origweak = g->weak; origall = g->allweak;
separatetobefnz(g, 0); /* separate objects to be finalized */
work += markbeingfnz(g); /* mark objects that will be finalized */
work += propagateall(g); /* remark, to propagate 'resurrection' */
9 years ago
convergeephemerons(g);
/* at this point, all resurrected objects are marked. */
/* remove dead objects from weak tables */
clearbykeys(g, g->ephemeron); /* clear keys from all ephemeron tables */
clearbykeys(g, g->allweak); /* clear keys from all 'allweak' tables */
9 years ago
/* clear values from resurrected weak tables */
clearbyvalues(g, g->weak, origweak);
clearbyvalues(g, g->allweak, origall);
9 years ago
luaS_clearcache(g);
6 years ago
g->currentwhite = cast_byte(otherwhite(g)); /* flip current white */
lua_assert(g->gray == NULL);
return work; /* estimate of slots marked by 'atomic' */
9 years ago
}
6 years ago
static int sweepstep (lua_State *L, global_State *g,
int nextstate, GCObject **nextlist) {
9 years ago
if (g->sweepgc) {
l_mem olddebt = g->GCdebt;
int count;
g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX, &count);
6 years ago
g->GCestimate += g->GCdebt - olddebt; /* update estimate */
return count;
}
else { /* enter next state */
g->gcstate = nextstate;
g->sweepgc = nextlist;
return 0; /* no work done */
9 years ago
}
}
6 years ago
static lu_mem singlestep (lua_State *L) {
9 years ago
global_State *g = G(L);
switch (g->gcstate) {
case GCSpause: {
restartcollection(g);
g->gcstate = GCSpropagate;
return 1;
9 years ago
}
case GCSpropagate: {
if (g->gray == NULL) { /* no more gray objects? */
g->gcstate = GCSenteratomic; /* finish propagate phase */
return 0;
}
else
return propagatemark(g); /* traverse one gray object */
9 years ago
}
case GCSenteratomic: {
lu_mem work = atomic(L); /* work is what was traversed by 'atomic' */
entersweep(L);
6 years ago
g->GCestimate = gettotalbytes(g); /* first estimate */;
return work;
9 years ago
}
6 years ago
case GCSswpallgc: { /* sweep "regular" objects */
return sweepstep(L, g, GCSswpfinobj, &g->finobj);
9 years ago
}
6 years ago
case GCSswpfinobj: { /* sweep objects with finalizers */
return sweepstep(L, g, GCSswptobefnz, &g->tobefnz);
9 years ago
}
6 years ago
case GCSswptobefnz: { /* sweep objects to be finalized */
return sweepstep(L, g, GCSswpend, NULL);
9 years ago
}
6 years ago
case GCSswpend: { /* finish sweeps */
9 years ago
checkSizes(L, g);
g->gcstate = GCScallfin;
return 0;
}
6 years ago
case GCScallfin: { /* call remaining finalizers */
if (g->tobefnz && !g->gcemergency) {
int n = runafewfinalizers(L, GCFINMAX);
return n * GCFINALIZECOST;
9 years ago
}
6 years ago
else { /* emergency mode or no more finalizers */
g->gcstate = GCSpause; /* finish collection */
9 years ago
return 0;
}
}
default: lua_assert(0); return 0;
}
}
6 years ago
9 years ago
/*
** advances the garbage collector until it reaches a state allowed
** by 'statemask'
*/
6 years ago
void luaC_runtilstate (lua_State *L, int statesmask) {
9 years ago
global_State *g = G(L);
6 years ago
while (!testbit(statesmask, g->gcstate))
singlestep(L);
9 years ago
}
6 years ago
9 years ago
/*
** Performs a basic incremental step. The debt and step size are
** converted from bytes to "units of work"; then the function loops
** running single steps until adding that many units of work or
** finishing a cycle (pause state). Finally, it sets the debt that
** controls when next step will be performed.
9 years ago
*/
static void incstep (lua_State *L, global_State *g) {
int stepmul = (getgcparam(g->gcstepmul) | 1); /* avoid division by 0 */
l_mem debt = (g->GCdebt / WORK2MEM) * stepmul;
l_mem stepsize = (g->gcstepsize <= log2maxs(l_mem))
? ((cast(l_mem, 1) << g->gcstepsize) / WORK2MEM) * stepmul
: MAX_LMEM; /* overflow; keep maximum value */
do { /* repeat until pause or enough "credit" (negative debt) */
lu_mem work = singlestep(L); /* perform one single step */
debt -= work;
} while (debt > -stepsize && g->gcstate != GCSpause);
if (g->gcstate == GCSpause)
setpause(g); /* pause until next cycle */
9 years ago
else {
debt = (debt / stepmul) * WORK2MEM; /* convert 'work units' to bytes */
luaE_setdebt(g, debt);
9 years ago
}
9 years ago
}
/*
** performs a basic GC step if collector is running
9 years ago
*/
6 years ago
void luaC_step (lua_State *L) {
9 years ago
global_State *g = G(L);
lua_assert(!g->gcemergency);
if (g->gcrunning) { /* running? */
if(isdecGCmodegen(g))
genstep(L, g);
else
incstep(L, g);
9 years ago
}
}
6 years ago
9 years ago
/*
** Perform a full collection in incremental mode.
9 years ago
** Before running the collection, check 'keepinvariant'; if it is true,
** there may be some objects marked as black, so the collector has
** to sweep all objects to turn them back to white (as white has not
** changed, nothing will be collected).
*/
static void fullinc (lua_State *L, global_State *g) {
if (keepinvariant(g)) /* black objects? */
6 years ago
entersweep(L); /* sweep everything to turn them back to white */
9 years ago
/* finish any pending sweep phase to start a new cycle */
luaC_runtilstate(L, bitmask(GCSpause));
6 years ago
luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */
9 years ago
/* estimate must be correct after a full GC cycle */
lua_assert(g->GCestimate == gettotalbytes(g));
6 years ago
luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */
9 years ago
setpause(g);
}
/*
** Performs a full GC cycle; if 'isemergency', set a flag to avoid
** some operations which could change the interpreter state in some
** unexpected ways (running finalizers and shrinking some structures).
*/
void luaC_fullgc (lua_State *L, int isemergency) {
global_State *g = G(L);
lua_assert(!g->gcemergency);
g->gcemergency = isemergency; /* set flag */
if (g->gckind == KGC_INC)
fullinc(L, g);
else
fullgen(L, g);
g->gcemergency = 0;
}
9 years ago
/* }====================================================== */
6 years ago