+// 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--;
+ }
+ if(g->waitCount >= g->nThreads - 1) {
+ freeDead();
+ if(g->needGC) garbageCollect();
+ if(g->waitCount) naSemUp(g->sem, g->waitCount);
+ g->bottleneck = 0;
+ }
+}
+
+void naCheckBottleneck()
+{
+ if(globals->bottleneck) { LOCK(); bottleneck(); UNLOCK(); }