vbSlacker's memory manager API.

If this rambles, I'm sorry; it's just been rewritten so many times, that I've
lost interest in keeping it coherent.

vbSlacker offers two levels of memory management for the C programmer.
These techniques are used in BASIClib as well as code generated by the
parser/compiler, and may also be used by linked C code, if so desired.

The levels are, abstractly:
- standard C library's malloc(), realloc(), free(), etc...
- BASIClib's __memAlloc(), __memRealloc(), __memFree(), etc...


Let's analyze each of these separately, shall we?

STANDARD C LIBRARY MEMORY MANAGEMENT LEVEL:

First, the programmer may always call malloc(), realloc(), free(), and
related functions like calloc(). These are always handy, since they are
portable between platforms, and pretty flexible at the expense of having
to check errors from every call manually. BASIClib knows nothing of calls
to these functions, so they will not trigger BASIC runtime errors, and
they will not interfere with the other higher-level memory management
API.

In short, the C library functions just like you'd expect it would.

Never mix calls to the C library's memory management functions with
BASIClib's. That is, don't call __memFree() on something you've malloc()ed,
don't call realloc() on things you've __memAlloc()ed, etc...

...and you should probably, for your own ease of coding, avoid these calls
anyhow, but it's nice to know they're there. Some of the lower-level sections
of BASIClib use malloc() and free()...

The main reason to avoid these are as follows:
- They return NULL on error, and you need to check for this manually.
- They cause memory leaks when you forget to call free().
- They cause memory leaks if a runtime error is thrown before you call free().

To prevent problems when using these functions, you need to do the following:
- Always check to make sure NULL is not returned, and handle it gracefully
  if it is. No unnecessary termination, and DEFINITELY no segmentation faults!
- Always double (and triple) check your logic for leaks.
- Always avoid functions that'll trigger BASIC runtime errors (all BASIClib
  functions are capable of doing so), and if you STILL must use malloc(), set
  up an error handler that free()s your memory, and rethrows the error. In so
  many words: it's a bitch.


BASICLIB MEMORY MANAGEMENT LEVEL:

BASIClib has it's own versions of malloc(), realloc(), and free(). They
follow internal BASIClib naming conventions, but other than that, the syntax
is the same. Therefore, the BASIClib equivalents of the C library are:

void *__memAlloc(__long memLength);
void *__memRealloc(void *ptr, __long memLength);
void __memFree(void *ptr);

(The implementation of calloc() is left as an exercise to the reader. If you
can't do this, then you probably can't hack it as a programmer anyhow.)

The difference? First, if the requested memory object cannot be allocated,
BASIClib tries to free unneeded memory, and allocate again. When this option
is completely exhausted, BASIC runtime error #7 (ERR_OUT_OF_MEMORY) is thrown.
After all, what do you plan to do when you run out of memory? These functions
never return NULL. __memFree() should be used just like free(), but only on
values returned by __memAlloc() and __memRealloc(). There is no guarantee
that __memAlloc() is just another layer over malloc(), either, so don't
expect that free() will necessarily be an acceptable replacement for
__memFree(). Maybe we used sbrk() or mmap() instead of malloc(). And maybe
we'll write our own real garbage collector someday, in which case __memFree()
will do a lot more. At any rate, just don't mix APIs. I can't stress this
enough.

The most important reason for using these functions instead of their standard C
counterparts is garbage collection.

First, a little history...
The problem with C and C++ (and lower-level languages) is that memory
management is done manually, which leads to potential "memory leaks". One
solution is to implement a GARBAGE COLLECTOR, which scans an application's
heap, stack, and static data to find if pointers to allocated memory still
exist. In the absence of a given pointer, the allocated object is considered
"garbage" (since there's no pointers to it, it's inaccessable, so it's just
taking up space.) and is free()d. Recent studies have shown that modern
garbage collection algorithms are as fast (if not faster) than manual memory
management (especially when you have to do some real programming aerobics to
keep track of all your allocated objects), and remove the problem of memory
leakage.

That doesn't make such systems perfect, however. The problem with such systems
is that they have to work against C/C++. They are not portable in the least
between hardware architectures (and frequently even operating systems on the
same architecture), they are somewhat contrary to concepts like destructors,
they don't like pointer arithmetic, and data alignment is hard to control in
C and impossible to control in C portably.

Did we mention that garbage collectors are a bitch to implement?

That's not to say it hasn't been tried. A dude name Boehm wrote a little piece
of programming brillance: a C/C++ friendly (er, somewhat...) Garbage Collector.

It's largely portable, too. And most importantly, Cygnus's native Java compiler
uses it for generated code. This means it's written and supported, and
frequently debugged, in an open-source manner, but it's still a funded project.
And we are going to reap the benefits of it. Oh, yes.

BASIClib presents an API which is a subset of the Boehm garbage collector's
functionality. All the important stuff is there, and the crap is cut out. If
you use cygnus's libgcj, you already have Boehm's collector, and BASIClib makes
calls to this library.

__memAlloc() and __memRealloc() pointers are automagically garbage collected
when there are no more references to them. This is very good. No memory leaks.
You might use more memory in the meantime, since the collector has an
allocation threshold; it will not try to free memory until it allocates
X pages (or runs out of memory prematurely). At that time, the garbage
collector is forced to run, and there may be a slight delay as it does so.
Both problems can be averted if you find opportune (read: idle) times in your
algorithms to ask the collector to run.

This is done with the following calls:

void __memDoFullCollect(void);     /* to collect every possible byte. */
void __memDoPartialCollect(void);  /* to collect only a little. */

Partial collection may run significantly faster, at the cost of having less
memory released.

Another way to speed up garbage collection is to use:

void *__memAllocNoPtrs(__long amount)

This instructs the garbage collector that the allocated object will never
store pointers. This means it doesn't need to be scanned for pointers to
collectable objects, which means less scanning. Naturally, this means you
can't store pointers in them, but they're good for allocating string data, and
such.

There are some programming restrictions on garbage collection, though:

 - Do not write pointers to disk or any other device for later retrieval.
   Actually, never do this anyway. If the only copy of a pointer is not in
   memory, the collector is going to think the object is garbage, and collect
   it prematurely. If you REALLY need to do something strange like this,
   use malloc(), so it won't be collected.

 - If you do any pointer arithmatic, keep at least one copy of the original
   pointer around until you are done. For example:

    char *feh = __memAlloc(10);
    feh++;

   means that your allocated object may get freed before you are done with it,
   since the value from __memAlloc no longer exists in memory. You should do
   this instead:

    char *tmp = __memAlloc(10);
    char *feh = tmp;
    feh++;

 - Global and static pointers should be set to NULL when you are done with
    them. Otherwise, a reference still exists, and the collector will not
    collect. Local data will be overwritten sooner or later, but it couldn't
    hurt to NULL this out, too.

 - Don't ever count on collection to free a pointer. It'll be freed when the
   collector deems it worthy to be freed. If there is even a possibility that
   a piece of memory is a pointer (i.e. - the pointer is 0x1234, and there's
   an int on the stack == 0x1234, the collector assumes the pointer exists.
   This is for safety's sake, and clashes are actually pretty rare.) If you
   must get rid of an allocated object, use __memFree() to explicitly free it.

 - malloc(), calloc(), and realloc() pointers are never garbage collected. This
   is a blessing and curse. BASIClib uses garbage collection almost everywhere,
   as will compiled BASIC code, but external C code doesn't need to. Don't
   count on external code to behave properly when it comes to memory management.

 - Don't store pointers in memory allocated with __memAllocNoPtrs().

 - Objects allocated with __memAllocNoPtrs() will keep their "no pointer" state
    when reallocated with __memRealloc(). Objects allocated with __memAlloc()
    will still be assumed to be holding pointers even after reallocation with
    __memRealloc()...in short, the flags remain, even if a different pointer
    is returned. calling __memRealloc(NULL, x) will return a newly allocated
    object that is assumed to hold pointers, even though this is usually an
    unadvised coding habit.

/* end of mem_management.txt ... */
