Previous: , Up: objects memory   [Index]


13.3.5 Writing correct C language code

The garbage collector considers an object “in use” if at least one reference to it is reachable from the roots of the garbage collection; the roots of the garbage collection are:

Notice that the heap’s nursery is not a garbage collector root; so if we leave some machine words uninitialised on the nursery, outside of Scheme objects: nothing bad happens, because the garbage collector never sees them. Upon allocation, there is no need to initialise the memory segment used as nursery.

If an ikptr_t reference exists only in a CPU register or on the C language stack, or on the C language heap out of segments allocated for Scheme: the garbage collector will not see it. This allows to avoid scanning the full process’ stack for references to values, but imposes care when writing C language code.

Whenever we call ik_safe_alloc() or a function relying on it for memory allocation: a garbage collection may run and Scheme objects may be moved from their location in memory to another memory generational page; this makes invalid all the pointers in the CPU registers, on the C stack and the C heap. Notice that this includes the arguments to C functions called from Scheme through the macro foreign-call.

If an old Scheme object contains a reference to a new Scheme object: we have to inform the garbage collector about this. Whenever we allocate a new Scheme object and store in one of its fields a reference to a previously allocated Scheme object: we have to register this event in the dirty vector.

We must write C code with the following constraints:

To help identification of C functions and macros allocating memory: the ones calling ik_safe_alloc() are prefixed with ika_ and IKA_; the ones calling ik_unsafe_alloc() are prefixed with iku_ and IKU_.

Notice that, according to the C standard Section 6.5.16 “Assignment operators”: the order of evaluation of the operands is unspecified3. In the following code:

IK_CAR(s_pair) = ika_bytevector_alloc(pcb, 8); /* WRONG */

the left–side expression may be evaluated before the right–side one, resulting in the value referenced by s_pair to be invalid when the memory assigment actually takes place; so we have to code:

ikpcb_t * pcb    = ...;
ikptr_t   s_pair = ...;
ikptr_t   s_tmp;

pcb->root0 = &s_pair;
{
  s_tmp          = ika_bytevector_alloc(pcb, 8); /* GOOD */
  IK_CAR(s_pair) = s_tmp;
  IK_SIGNAL_DIRT(pcb, IK_CAR_PTR(s_pair));
}
pcb->root0 = NULL;

or:

ikpcb_t * pcb    = ...;
ikptr_t   s_pair = ...;
ikptr_t   s_tmp;

pcb->root0 = &s_pair;
{
  IK_ASS(IK_CAR(s_pair), ika_bytevector_alloc(pcb, 8)); /* GOOD */
  IK_SIGNAL_DIRT(pcb, IK_CAR_PTR(s_pair));
}
pcb->root0 = NULL;

yes, it is a hard life.

Let’s consider the following snippet, which is wrong:

ikpcb_t * pcb = ik_the_pcb();
ikptr_t   s_one, s_two;

s_one = IKA_PAIR_ALLOC(pcb); /* WRONG */
pcb->root0 = &s_one;
{
  s_two = IKA_PAIR_ALLOC(pcb);
}
pcb->root0 = NULL;

when the second pair is allocated, the first pair has car and cdr still uninitialised (the macro IKA_PAIR_ALLOC() does not initialise the pair object): the content of these words is undefined; this may cause undefined behaviour while the second allocation takes place and the garbage collection tries to scan the first pair. The correct code is:

ikpcb_t * pcb = ik_the_pcb();
ikptr_t   s_one, s_two;

s_one = IKA_PAIR_ALLOC(pcb);
IK_CAR(s_one) = IK_FALSE; /* GOOD */
IK_CDR(s_one) = IK_FALSE; /* GOOD */
pcb->root0 = &s_one;
{
  s_two = IKA_PAIR_ALLOC(pcb);
}
pcb->root0 = NULL;

or:

ikpcb_t * pcb = ik_the_pcb();
ikptr_t   s_one, s_two;

s_one = ika_pair_alloc(pcb); /* GOOD */
pcb->root0 = &s_one;
{
  s_two = IKA_PAIR_ALLOC(pcb);
}
pcb->root0 = NULL;

because ika_pair_alloc() initialises the car and the cdr.


Footnotes

(3)

For an introduction to such problems see (URL last verified Jan 12, 2012):

http://en.wikipedia.org/wiki/Sequence_point

Previous: , Up: objects memory   [Index]