How the Nasal GC works: Difference between revisions

Jump to navigation Jump to search
m
Line 3: Line 3:


= NaRef =
= NaRef =
= Pool storage =
A memory pool is basically a preallocated region of memory
The interesting part is the naGC_get() function which isn't directly mapped to naAlloc() or malloc(), but instead manages the Nasal memory pools.
For each native Nasal data type (scalars, vectors, hashes, funcs etc) , there are separate memory pools in globals->pools[n]. The index (n) is one of T_VEC, T_HASH, T_FUNC etc.
== Nasal memory pools ==
The layout of the Nasal memory structure pool can be found in data.h, line 172: https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/data.h#line172
<syntaxhighlight lang="C">
struct naPool {
    int          type;
    int          elemsz;
    struct Block* blocks;
    void**  free0; // pointer to the alloced buffer
    int    freesz; // size of the alloced buffer
    void**    free; // current "free frame"
    int      nfree; // down-counting index within the free frame
    int    freetop; // curr. top of the free list
};
</syntaxhighlight>
For each of the 7 Nasal data types, there is a separate storage pool available (0..6). Each storage pool is addressed by its enum index: https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/data.h#line65
<syntaxhighlight lang="C">
enum { T_STR, T_VEC, T_HASH, T_CODE, T_FUNC, T_CCODE, T_GHOST, NUM_NASAL_TYPES }; // V. important that this come last!
</syntaxhighlight>
For example, Globals->pools[T_VEC] refers to the storage pools  for vectors:
<syntaxhighlight lang="C">
Globals->pools[T_VEC];
</syntaxhighlight>
The 7 storage pools are declared in code.h as part of the "Globals" structure, line 39: https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/code.h#line39
<syntaxhighlight lang="C">
struct Globals {
    // Garbage collecting allocators:
    struct naPool pools[NUM_NASAL_TYPES];
    int allocCount;
    // Dead blocks waiting to be freed when it is safe
    void** deadBlocks;
    int deadsz;
    int ndead;
    // Threading stuff
    int nThreads;
    int waitCount;
    int needGC;
    int bottleneck;
    void* sem;
    void* lock;
    // Constants
    naRef meRef;
    naRef argRef;
    naRef parentsRef;
    // A hash of symbol names
    naRef symbols;
    naRef save;
    struct Context* freeContexts;
    struct Context* allContexts;
};
</syntaxhighlight>
Each memory pool must be initialized using naGCInit(): https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/gc.c#line175
<syntaxhighlight lang="C">
void naGC_init(struct naPool* p, int type)
{
    p->type = type;
    p->elemsz = naTypeSize(type);
    p->blocks = 0;
    p->free0 = p->free = 0;
    p->nfree = p->freesz = p->freetop = 0;
    reap(p);
}
</syntaxhighlight>
The size of a memory pool is determined using poolsize(): https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/gc.c#line186
<syntaxhighlight lang="C">
static int poolsize(struct naPool* p)
{
    int total = 0;
    struct Block* b = p->blocks;
    while(b) { total += b->size; b = b->next; }
    return total;
}
</syntaxhighlight>
== The pool manager ==
All high level type allocators (naNewString, naNewVector, naNewHash etc) make use of the naNew() call introduced earlier.
naNew() doesn't directly allocate new memory from the heap, but instead use the naGC_get() helper which manages pool memory.
The naGC_get() pool "manager" is located in gc.nas line 194: https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/gc.c#line194
<syntaxhighlight lang="C">
struct naObj** naGC_get(struct naPool* p, int n, int* nout)
{
    struct naObj** result;
    naCheckBottleneck();
    LOCK();
    while(globals->allocCount < 0 || (p->nfree == 0 && p->freetop >= p->freesz)) {
        globals->needGC = 1;
        bottleneck();
    }
    if(p->nfree == 0)
        newBlock(p, poolsize(p)/8);
    n = p->nfree < n ? p->nfree : n;
    *nout = n;
    p->nfree -= n;
    globals->allocCount -= n;
    result = (struct naObj**)(p->free + p->nfree);
    UNLOCK();
    return result;
}
</syntaxhighlight>
As can be seen, the garbage collector itself is triggered by setting the global "needGC" flag to 1 in the globals structure and then calling the bottleneck() function: [https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/gc.c#line199].
In line 209, the memory region from the global pool is cast back to a pointer (struct naObj**): https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/gc.c#line209
the "nout" paramter is just there to update c->nfree[type] with the amount of free memory.
== Memory blocks ==
A memory block is a singly linked list: https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/gc.c#line10
<syntaxhighlight lang="C">
struct Block {
    int  size;
    char* block;
    struct Block* next;
};
</syntaxhighlight>
New memory blocks are allocated using newBlock(): https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/gc.c#line150
<syntaxhighlight lang="C">
static void newBlock(struct naPool* p, int need)
{
    int i;
    struct Block* newb;
    if(need < MIN_BLOCK_SIZE) need = MIN_BLOCK_SIZE;
    newb = naAlloc(sizeof(struct Block));
    newb->block = naAlloc(need * p->elemsz);
    newb->size = need;
    newb->next = p->blocks;
    p->blocks = newb;
    naBZero(newb->block, need * p->elemsz);
    if(need > p->freesz - p->freetop) need = p->freesz - p->freetop;
    p->nfree = 0;
    p->free = p->free0 + p->freetop;
    for(i=0; i < need; i++) {
        struct naObj* o = (struct naObj*)(newb->block + i*p->elemsz);
        o->mark = 0;
        p->free[p->nfree++] = o;
    }
    p->freetop += need;
}
</syntaxhighlight>
All core memory allocation takes place via wrappers in [https://gitorious.org/fg/simgear/blobs/next/simgear/nasal/misc.c#line8 misc.c]:
<syntaxhighlight lang="C">
void naFree(void* m) { free(m); }
void* naAlloc(int n) { return malloc(n); }
void* naRealloc(void* b, int n) { return realloc(b, n); }
void naBZero(void* m, int n) { memset(m, 0, n); }
</syntaxhighlight>


= Allocating Nasal types =
= Allocating Nasal types =

Navigation menu