-// Decremented every allocation. When it reaches zero, we do a
-// garbage collection. The value is reset to 1/2 of the total object
-// count each collection, which is sane: it ensures that no more than
-// 50% growth can happen between collections, and ensures that garbage
-// collection work is constant with allocation work (i.e. that O(N)
-// work is done only every O(1/2N) allocations).
-static int GlobalAllocCount = 256;
-
-static void appendfree(struct naPool*p, struct naObj* o)
-{
- // Need more space?
- if(p->freesz <= p->nfree) {
- int i, n = 1+((3*p->nfree)>>1);
- void** newf = naAlloc(n * sizeof(void*));
- for(i=0; i<p->nfree; i++)
- newf[i] = p->free[i];
- naFree(p->free);
- p->free = newf;
- p->freesz = n;
+// Must be called with the giant exclusive lock!
+static void freeDead()
+{
+ int i;
+ for(i=0; i<globals->ndead; i++)
+ naFree(globals->deadBlocks[i]);
+ globals->ndead = 0;
+}
+
+static void marktemps(struct Context* c)
+{
+ int i;
+ naRef r = naNil();
+ for(i=0; i<c->ntemps; i++) {
+ SETPTR(r, c->temps[i]);
+ mark(r);
+ }
+}
+
+// Must be called with the big lock!
+static void garbageCollect()
+{
+ int i;
+ struct Context* c;
+ globals->allocCount = 0;
+ c = globals->allContexts;
+ while(c) {
+ for(i=0; i<NUM_NASAL_TYPES; i++)
+ c->nfree[i] = 0;
+ for(i=0; i < c->fTop; i++) {
+ mark(c->fStack[i].func);
+ mark(c->fStack[i].locals);
+ }
+ for(i=0; i < c->opTop; i++)
+ mark(c->opStack[i]);
+ mark(c->dieArg);
+ marktemps(c);
+ c = c->nextAll;
+ }
+
+ mark(globals->save);
+ mark(globals->symbols);
+ mark(globals->meRef);
+ mark(globals->argRef);
+ mark(globals->parentsRef);
+
+ // Finally collect all the freed objects
+ for(i=0; i<NUM_NASAL_TYPES; i++)
+ reap(&(globals->pools[i]));
+
+ // Make enough space for the dead blocks we need to free during
+ // execution. This works out to 1 spot for every 2 live objects,
+ // which should be limit the number of bottleneck operations
+ // without imposing an undue burden of extra "freeable" memory.
+ if(globals->deadsz < globals->allocCount) {
+ globals->deadsz = globals->allocCount;
+ if(globals->deadsz < 256) globals->deadsz = 256;
+ naFree(globals->deadBlocks);
+ globals->deadBlocks = naAlloc(sizeof(void*) * globals->deadsz);
+ }
+ globals->needGC = 0;
+}
+
+void naModLock()
+{
+ LOCK();
+ globals->nThreads++;
+ UNLOCK();
+ naCheckBottleneck();
+}
+
+void naModUnlock()
+{
+ LOCK();
+ globals->nThreads--;
+ // We might be the "last" thread needed for collection. Since
+ // we're releasing our modlock to do something else for a while,
+ // wake someone else up to do it.
+ if(globals->waitCount == globals->nThreads)
+ naSemUp(globals->sem, 1);
+ UNLOCK();
+}
+
+// Must be called with the main lock. Engages the "bottleneck", where
+// all threads will block so that one (the last one to call this
+// function) can run alone. This is done for GC, and also to free the
+// list of "dead" blocks when it gets full (which is part of GC, if
+// you think about it).
+static void bottleneck()
+{
+ struct Globals* g = globals;
+ g->bottleneck = 1;
+ while(g->bottleneck && g->waitCount < g->nThreads - 1) {
+ g->waitCount++;
+ UNLOCK(); naSemDown(g->sem); LOCK();
+ g->waitCount--;