Design‎ > ‎

Unmanaged memory

Allocating memory and freeing it again later has overhead. Also, the garbage collector is convenient
but adds a bit of overhead and frees memory long after it is no longer used, increasing the memory
use of a program. There are other ways to use memory:
  1. use the stack
  2. use static memory
  3. manual allocation on the heap
Another language that uses these ways is C++. Zimbu will offer these methods on top of managed memory,
thus you have the best of both worlds.

1. Use the stack

Using the stack frame is much more efficient. Extending the stack on a method call and unwinding it
on a method return is done anyway, thus using the stack to store objects basically comes for free.

As a first step to support objects that are not allocated the programmer has to explicitly mark variables
(see below for automaticcally doing this):

PROC hello()
  list<string> onHeap
  list<string> %onStack
   ...
}
list<string> %inStatic

The % character is used to indicate the variable lives on the stack or in static memory.  Compare this to using $
for object members. This way the programmer knows at every point in the method where the variable is used that
is not allocated. This is important for how the variable can be used, especially the ones in a method that live on
the stack: Such a variable cannot be used after the method returns. Because once the method returns that portion
of the stack is no longer available.

That includes storing a reference to the object on the stack in any other object or container that is still in use
when the method returns. Even when that is for just a moment and when that object is not actually used.  That
it exists is already a problem. Suppose methodA has a list on the stack, and it calls methodB, which
uses that list to store an item which lives on the stack.  methodA does not use that item, e.g., it clears
the list after methodB returns. But the GC may run between methodB returning and the list being cleared, and then the
GC will inspect the list of methodA and encounter the object that was on the stack of methodB and is now invalid.

The compiler will check for violation of the rules as much as it can.  Once this is perfect objects can be put on
the stack automatically, see below.

String and bytes cannot be put on the stack, because they are either a constant, for which there is no reason to
put them on the stack, or the size is unknown at compilation time, in which case they need to be allocated anyway.

Initialization

An object on the stack is not initialized at first. NEW() must be called to do that:

PROC hello()
  list<string> %onStack = NEW()
  Foo          %aFoo
   ...
  %aFoo = NEW()
   ...
  %onStack = ["one", "two", "three"]
}

The initialization can be done with the declaration or later, before the object is used.

Note that initialization is the only kind of assignment that is allowed.

2. Use static memory

Another part of memory that can be used is static memory.  Variables for which there exists only one
in the whole program (inside the SHARED section of a class, at the module level or in the main file)
can be put in static memory.

The advantage over using managed memory is that the garbage collector does not need to go over
these objects, since they will never be freed. Most global variables can use static memory, unless they
need to store a reference to an object instead of the object itself.

This works the same as objects on the stack: Declare the variable with a % prepended.  The same rules
apply for using NEW() and not being able to assign to the variable.

To make lazy initialization work the same way as with objects on the heap, one can
compare the not allocated object with NIL:
IF %globalList == NIL
  %globalList = NEW()
}

3. Manual allocation and free

When a program needs complete control over memory use, e.g. for near-realtime programs or on systems
with a limited amount of memory, using a garbage collector is undesired.  This comes at a price though: the
programmer must take care to free memory correctly.  This can easily lead to memory leaks or double frees.

PROC hello()
  list<string> onHeap = HEAP.NEW()
  Foo          aFoo
   ...
  aFoo = HEAP.NEW()
   ...
  onStack = HEAP.["one""two""three"]
   ...
  HEAP.free(onHeap)
  HEAP.free(aFoo)
}

Note that manually allocated objects and garbage collected objects can be mixed as you like.
Thus a manually allocated list can contain items that are garbage collected and the other way around.
However, you must make sure that you keep a reference to anything that contains manually managed
memory, otherwise you cannot free it and the memory leaks!

A variable can hold a manually allocated object at one time, and a managed object at another time.
Just be sure to free the manually allocated objects.  This is where it goes wrong:

PROC hello()
  Foo          aFoo
   ...
  aFoo = HEAP.NEW()
   ...
  aFoo = NEW()     # ERROR: Previous Foo leaks!
   ...
  HEAP.free(aFoo)  # ERROR: Freeing a managed object!
}

Making sure memory is freed is difficult. Here are a few hints:
  • Make clear who owns the object.  The owner is responsible for calling free later.
  • Use try/finally to make sure objects are freed even when an exception is thrown.  This is expensive though.
  • Use DEFER (not implemented yet). This is much cheaper and keeps the free close to the allocation.
  • Use the Finish() method of the object to free members. Especially useful for objects on the stack.

Automatic use of the stack and static memory

Having the programmer explicitly declare objects to live on the stack is an intermediate step.  Eventually the
compiler will do this automatically.  It's not going to be easy though.  When a method is called with an object
as argument, the compiler will somehow have to figure out what happens with the object inside that method.
If it is stored in an allocated object, or stored in an object that still exists after the method returns, then the object
passed as an argument cannot be on the stack.  Initially every object can be flagged as on the stack, and then
the compiler must reset the flag if any of the rules are violated.

Doing this for global variables is much simpler, since they live forever.

Comments