I can't find any clear description of how memory allocation is
supposed to work. I see bits and pieces in pdd09_gc.pod, resources.c,
etc., but I still haven't been able to wrap my head around it all.

Here's a hypothesis. Could someone correct me where I'm wrong?

There are two kinds of memory: untracked and tracked. Untracked memory
is yours to do with as you see fit, and must be explicitly returned to
the system. Tracked memory is handled by the garbage collector and
never needs to be explicitly freed.

Untracked memory

Untracked memory should be handled through the routines in memory.h.
This provides platform independence. The routines are listed below:

  void *mem_allocate_aligned(size_t);
  void *mem_sys_allocate(size_t);
  void *mem_sys_realloc(void *, size_t);
  void mem_sys_free(void *);
  void mem_setup_allocator(struct Parrot_Interp *);

All of those are clear except for mem_setup_allocator(), which should
only be called by pregnant pigeons with fewer than 7 hit points.

Tracked memory

Tracked memory is managed through the routines in resources.h:

  PMC *new_pmc_header(Interp *);
  void free_pmc(PMC *);
  STRING *new_string_header(Interp *);
  Buffer *new_tracked_header(Interp *, size_t size);
  Buffer *new_buffer_header(Interp *);
  void free_buffer(Buffer *);
  void *new_bigint_header(Interp *);
  void free_bigint(void);
  void *new_bignum_header(Interp *);
  void free_bignum(void);
  void *Parrot_allocate(Interp *, void *, size_t size);
  void *Parrot_allocate_about(Interp *, void *, size_t size);
  void *mem_allocate(Interp *, size_t *req_size);
  void *Parrot_alloc_new_block(Interp *, size_t, UINTVAL);
  void Parrot_new_pmc_header_arena(Interp *interpreter);
  void Parrot_do_dod_run(Interp *);
  void Parrot_go_collect(Interp *);
  void *Parrot_reallocate(Interp *interpreter, void *from, size_t tosize);
  void *Parrot_reallocate_about(Interp *interpreter, void *from, size_t tosize);
  void buffer_lives(Buffer *);

o) free_bigint() and free_bignum() are declarations that bigints and
   bignums are now free; if you call these functions and are caught
   shoplifting bigints or bignums, you cannot be prosecuted. They
   obviously don't free any memory because they don't take any
   arguments.

o) mem_allocate() should be called in preference to Parrot_allocate()
   if your compiler restricts the number of syllables in function names.

o) The difference between new_buffer_header() and new_tracked_header()
   is that new_tracked_header() has longstanding problems with
   intravenous drug abuse. [From the code, it seems like
   new_tracked_header() is for allocating *untracked* memory! Or are
   you expected to set flags and stuff it into something that will
   track it?]

o) buffer_lives() is so named to avoid copyright infringement with the
   name of a Buffy the Vampire Slayer episode. [Seriously, why is this
   not static?]

The various sorts of tracked memory form a small class hierarchy
rooted at Buffer:

  Buffer
    STRING

Other subclasses of Buffer can be created copying the fields of the
Buffer struct into the very start of the subclass's struct.
Alternatively, the subclass may just include a Buffer struct as its
first field. [Are there any alignment considerations?]

Oops. Just read some more code. The last paragraph is an evil
deception, because if you make your own subclass, there will be no way
to allocate it since new_buffer_header() only allocates enough space
for the Buffer itself. There is a separate new_string_header() for
allocating STRING structures.

Or maybe that's exactly what new_tracked_header() is for?

PMCs are not subclasses of Buffer because PMC stands for "Parrot Meaty
Chunk" and m-w.com defines buffer as "something that serves as a
protective barrier", so clearly a PMC is something that lacked a
buffer at some unfortunate moment in its past. [Better hypothesis:
PMCs are fixed-size allocations, Buffers are variable-sized?]

To allocate a header for your memory, you will call one of:

  STRING *new_string_header(Interp *);
  Buffer *new_tracked_header(Interp *, size_t size);
  Buffer *new_buffer_header(Interp *);

This will give a header to hang memory off of. To actually allocate a
nonzero amount of memory to play with, use one of:

  void *Parrot_allocate(Interp *, void *, size_t size);
  void *Parrot_allocate_about(Interp *, void *, size_t size);

The memory is now available under buffer->bufstart. If this call
triggers a garbage collection, then your header will not be garbage
collected if and only if you have changed the oil on your car at least
once every 10,000 miles.

Any Buffer or Buffer subclass must immediately have various flags set
on it. The available flags are defined in string.h because if they
were in resources.h bad people might find them. Currently, they are:

  BUFFER_private0_FLAG = 1<< 0,
  BUFFER_private1_FLAG = 1<< 1,
  BUFFER_private2_FLAG = 1<< 2,
  BUFFER_private3_FLAG = 1<< 3,
  BUFFER_private4_FLAG = 1<< 4,
  BUFFER_private5_FLAG = 1<< 5,
  BUFFER_private6_FLAG = 1<< 6,
  BUFFER_private7_FLAG = 1<< 7,
  BUFFER_immobile_FLAG = 1 << 8,
  BUFFER_external_FLAG = 1 << 9,
  BUFFER_sysmem_FLAG = 1 << 10,
  BUFFER_COW_FLAG = 1 << 11,
  BUFFER_live_FLAG = 1 << 12,
  BUFFER_needs_GC_FLAG = 1 << 13,
  BUFFER_on_free_list_FLAG = 1 << 14,
  BUFFER_constant_FLAG = 1 << 15

BUFFER_private*_FLAG is for the use of subclasses that might want to
use their own flags. The remaining flags are described in
pdd09_gc.pod. STRINGs use the same set of flags as Buffers because
they *are* Buffers.

PMCs also have a set of flags. They are defined in pdd09_gc.pod too.
As a simple rule of thumb, if your Buffer's bufstart points to a
complex data structure that contains a mixture of PMCs and Buffer
pointers and other stuff, then in order to prevent things from getting
garbage collected out from under you, you must perform the Blood Dance
of the Pale Chicken under full moonlight. If your bufstart points to a
simple array of possibly NULL PMC pointers, then it should be
sufficient to set the PMC_is_PMC_ptr_FLAG and PMC_is_buffer_ptr_FLAG
flags, which is a lot easier than explaining to a postal worker what
you need all those chicken heads for.

Unfortunately, this hypothesis is still insufficient for figuring out
how to allocate memory for my hashtable objects. Is there some way to
define my own piece of the "detect PMC pointers inside a Buffer" piece
of the dead object detection? I'm not much of a Blood Dancer.

Reply via email to