]> git.mxchange.org Git - simgear.git/blob - simgear/nasal/gc.c
Platform compatibility fix.
[simgear.git] / simgear / nasal / gc.c
1 #include "nasal.h"
2 #include "data.h"
3 #include "code.h"
4
5 #define MIN_BLOCK_SIZE 256
6
7 static void reap(struct naPool* p);
8 static void mark(naRef r);
9
10 struct Block {
11     int   size;
12     char* block;
13     struct Block* next;
14 };
15
16 // Must be called with the giant exclusive lock!
17 static void freeDead()
18 {
19     int i;
20     for(i=0; i<globals->ndead; i++)
21         naFree(globals->deadBlocks[i]);
22     globals->ndead = 0;
23 }
24
25 static void marktemps(struct Context* c)
26 {
27     int i;
28     naRef r = naNil();
29     for(i=0; i<c->ntemps; i++) {
30         r.ref.ptr.obj = c->temps[i];
31         mark(r);
32     }
33 }
34
35 // Must be called with the big lock!
36 static void garbageCollect()
37 {
38     int i;
39     struct Context* c;
40     globals->allocCount = 0;
41     c = globals->allContexts;
42     while(c) {
43         for(i=0; i<NUM_NASAL_TYPES; i++)
44             c->nfree[i] = 0;
45         for(i=0; i < c->fTop; i++) {
46             mark(c->fStack[i].func);
47             mark(c->fStack[i].locals);
48         }
49         for(i=0; i < c->opTop; i++)
50             mark(c->opStack[i]);
51         mark(c->dieArg);
52         marktemps(c);
53         c = c->nextAll;
54     }
55
56     mark(globals->save);
57     mark(globals->symbols);
58     mark(globals->meRef);
59     mark(globals->argRef);
60     mark(globals->parentsRef);
61
62     // Finally collect all the freed objects
63     for(i=0; i<NUM_NASAL_TYPES; i++)
64         reap(&(globals->pools[i]));
65
66     // Make enough space for the dead blocks we need to free during
67     // execution.  This works out to 1 spot for every 2 live objects,
68     // which should be limit the number of bottleneck operations
69     // without imposing an undue burden of extra "freeable" memory.
70     if(globals->deadsz < globals->allocCount) {
71         globals->deadsz = globals->allocCount;
72         if(globals->deadsz < 256) globals->deadsz = 256;
73         naFree(globals->deadBlocks);
74         globals->deadBlocks = naAlloc(sizeof(void*) * globals->deadsz);
75     }
76     globals->needGC = 0;
77 }
78
79 void naModLock()
80 {
81     LOCK();
82     globals->nThreads++;
83     UNLOCK();
84     naCheckBottleneck();
85 }
86
87 void naModUnlock()
88 {
89     LOCK();
90     globals->nThreads--;
91     UNLOCK();
92 }
93
94 // Must be called with the main lock.  Engages the "bottleneck", where
95 // all threads will block so that one (the last one to call this
96 // function) can run alone.  This is done for GC, and also to free the
97 // list of "dead" blocks when it gets full (which is part of GC, if
98 // you think about it).
99 static void bottleneck()
100 {
101     struct Globals* g = globals;
102     g->bottleneck = 1;
103     while(g->bottleneck && g->waitCount < g->nThreads - 1) {
104         g->waitCount++;
105         UNLOCK(); naSemDown(g->sem); LOCK();
106         g->waitCount--;
107     }
108     if(g->waitCount >= g->nThreads - 1) {
109         freeDead();
110         if(g->needGC) garbageCollect();
111         if(g->waitCount) naSemUpAll(g->sem, g->waitCount);
112         g->bottleneck = 0;
113     }
114 }
115
116 void naCheckBottleneck()
117 {
118     if(globals->bottleneck) { LOCK(); bottleneck(); UNLOCK(); }
119 }
120
121 static void naCode_gcclean(struct naCode* o)
122 {
123     naFree(o->byteCode);   o->byteCode = 0;
124     naFree(o->constants);  o->constants = 0;
125     naFree(o->argSyms);    o->argSyms = 0;
126     naFree(o->optArgSyms); o->argSyms = 0;
127 }
128
129 static void naGhost_gcclean(struct naGhost* g)
130 {
131     if(g->ptr) g->gtype->destroy(g->ptr);
132     g->ptr = 0;
133 }
134
135 static void freeelem(struct naPool* p, struct naObj* o)
136 {
137     // Free any intrinsic (i.e. non-garbage collected) storage the
138     // object might have
139     switch(p->type) {
140     case T_STR:
141         naStr_gcclean((struct naStr*)o);
142         break;
143     case T_VEC:
144         naVec_gcclean((struct naVec*)o);
145         break;
146     case T_HASH:
147         naHash_gcclean((struct naHash*)o);
148         break;
149     case T_CODE:
150         naCode_gcclean((struct naCode*)o);
151         break;
152     case T_GHOST:
153         naGhost_gcclean((struct naGhost*)o);
154         break;
155     }
156
157     // And add it to the free list
158     p->free[p->nfree++] = o;
159 }
160
161 static void newBlock(struct naPool* p, int need)
162 {
163     int i;
164     struct Block* newb;
165
166     if(need < MIN_BLOCK_SIZE) need = MIN_BLOCK_SIZE;
167
168     newb = naAlloc(sizeof(struct Block));
169     newb->block = naAlloc(need * p->elemsz);
170     newb->size = need;
171     newb->next = p->blocks;
172     p->blocks = newb;
173     naBZero(newb->block, need * p->elemsz);
174     
175     if(need > p->freesz - p->freetop) need = p->freesz - p->freetop;
176     p->nfree = 0;
177     p->free = p->free0 + p->freetop;
178     for(i=0; i < need; i++) {
179         struct naObj* o = (struct naObj*)(newb->block + i*p->elemsz);
180         o->mark = 0;
181         p->free[p->nfree++] = o;
182     }
183     p->freetop += need;
184 }
185
186 void naGC_init(struct naPool* p, int type)
187 {
188     p->type = type;
189     p->elemsz = naTypeSize(type);
190     p->blocks = 0;
191
192     p->free0 = p->free = 0;
193     p->nfree = p->freesz = p->freetop = 0;
194     reap(p);
195 }
196
197 static int poolsize(struct naPool* p)
198 {
199     int total = 0;
200     struct Block* b = p->blocks;
201     while(b) { total += b->size; b = b->next; }
202     return total;
203 }
204
205 struct naObj** naGC_get(struct naPool* p, int n, int* nout)
206 {
207     struct naObj** result;
208     naCheckBottleneck();
209     LOCK();
210     while(globals->allocCount < 0 || (p->nfree == 0 && p->freetop >= p->freesz)) {
211         globals->needGC = 1;
212         bottleneck();
213     }
214     if(p->nfree == 0)
215         newBlock(p, poolsize(p)/8);
216     n = p->nfree < n ? p->nfree : n;
217     *nout = n;
218     p->nfree -= n;
219     globals->allocCount -= n;
220     result = (struct naObj**)(p->free + p->nfree);
221     UNLOCK();
222     return result;
223 }
224
225 static void markvec(naRef r)
226 {
227     int i;
228     struct VecRec* vr = r.ref.ptr.vec->rec;
229     if(!vr) return;
230     for(i=0; i<vr->size; i++)
231         mark(vr->array[i]);
232 }
233
234 static void markhash(naRef r)
235 {
236     int i;
237     struct HashRec* hr = r.ref.ptr.hash->rec;
238     if(!hr) return;
239     for(i=0; i < (1<<hr->lgalloced); i++) {
240         struct HashNode* hn = hr->table[i];
241         while(hn) {
242             mark(hn->key);
243             mark(hn->val);
244             hn = hn->next;
245         }
246     }
247 }
248
249 // Sets the reference bit on the object, and recursively on all
250 // objects reachable from it.  Uses the processor stack for recursion...
251 static void mark(naRef r)
252 {
253     int i;
254
255     if(IS_NUM(r) || IS_NIL(r))
256         return;
257
258     if(r.ref.ptr.obj->mark == 1)
259         return;
260
261     r.ref.ptr.obj->mark = 1;
262     switch(r.ref.ptr.obj->type) {
263     case T_VEC: markvec(r); break;
264     case T_HASH: markhash(r); break;
265     case T_CODE:
266         mark(r.ref.ptr.code->srcFile);
267         for(i=0; i<r.ref.ptr.code->nConstants; i++)
268             mark(r.ref.ptr.code->constants[i]);
269         break;
270     case T_FUNC:
271         mark(r.ref.ptr.func->code);
272         mark(r.ref.ptr.func->namespace);
273         mark(r.ref.ptr.func->next);
274         break;
275     }
276 }
277
278 // Collects all the unreachable objects into a free list, and
279 // allocates more space if needed.
280 static void reap(struct naPool* p)
281 {
282     struct Block* b;
283     int elem, freesz, total = poolsize(p);
284     p->nfree = 0;
285     freesz = total < MIN_BLOCK_SIZE ? MIN_BLOCK_SIZE : total;
286     freesz = (3 * freesz / 2) + (globals->nThreads * OBJ_CACHE_SZ);
287     if(p->freesz < freesz) {
288         naFree(p->free0);
289         p->freesz = freesz;
290         p->free = p->free0 = naAlloc(sizeof(void*) * p->freesz);
291     }
292
293     for(b = p->blocks; b; b = b->next)
294         for(elem=0; elem < b->size; elem++) {
295             struct naObj* o = (struct naObj*)(b->block + elem * p->elemsz);
296             if(o->mark == 0)
297                 freeelem(p, o);
298             o->mark = 0;
299         }
300
301     // allocs of this type until the next collection
302     globals->allocCount += total/2;
303     
304     // Allocate more if necessary (try to keep 25-50% of the objects
305     // available)
306     if(p->nfree < total/4) {
307         int used = total - p->nfree;
308         int avail = total - used;
309         int need = used/2 - avail;
310         if(need > 0)
311             newBlock(p, need);
312     }
313     p->freetop = p->nfree;
314 }
315
316 // Does the swap, returning the old value
317 static void* doswap(void** target, void* val)
318 {
319     void* old = *target;
320     *target = val;
321     return old;
322 }
323
324 // Atomically replaces target with a new pointer, and adds the old one
325 // to the list of blocks to free the next time something holds the
326 // giant lock.
327 void naGC_swapfree(void** target, void* val)
328 {
329     void* old;
330     LOCK();
331     old = doswap(target, val);
332     while(globals->ndead >= globals->deadsz)
333         bottleneck();
334     globals->deadBlocks[globals->ndead++] = old;
335     UNLOCK();
336 }