Previous: objects memory alloc, Up: objects memory [Index]
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:
root
fields of the PCB structure.
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:
ik_safe_alloc()
: we must make sure that all the
Scheme objects we are using in C code are reachable by the garbage
collector. This is done by registering an object as garbage collector
root through the root
fields of the PCB.
ik_safe_alloc()
: we must reobtain all the pointers
to the internals of the objects we are using.
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_
.
s_one
is protected while
allocating s_two
:
ikpcb_t * pcb = ik_the_pcb(); ikptr_t s_one, s_two; s_one = ika_bytevector_alloc(pcb, 10); pcb->root0 = &s_one; { s_two = ika_bytevector_alloc(pcb, 10); /* GOOD */ } pcb->root0 = NULL;
example of wrong code: after the second call to the allocation
function the value in s_one
may be invalid:
ikpcb_t * pcb = ik_the_pcb(); ikptr_t s_one, s_two; s_one = ika_bytevector_alloc(pcb, 10); s_two = ika_bytevector_alloc(pcb, 10); /* do something with "s_one" and "s_two" */ /* WRONG */
s_one
is protected while
allocating s_two
and after the second allocation the pointer to
the data area of s_one
is retrieved again:
ikpcb_t * pcb = ik_the_pcb(); ikptr_t s_one; ikptr_t s_two; char * one; char * two; s_one = ika_bytevector_alloc(pcb, 10); one = IK_BYTEVECTOR_DATA_CHARP(s_one); /* do something with "one" */ pcb->root0 = &s_one; { s_two = ika_bytevector_alloc(pcb, 10); } pcb->root0 = NULL; one = IK_BYTEVECTOR_DATA_CHARP(s_one); /* GOOD */ two = IK_BYTEVECTOR_DATA_CHARP(s_two); /* do something with "one" and "two" */
example of wrong code: after the second call to the allocation
function the pointer one
to the data area of s_one
may be
invalid:
ikpcb_t * pcb = ik_the_pcb(); ikptr_t s_one; ikptr_t s_two; char * one; char * two; s_one = ika_bytevector_alloc(pcb, 10); one = IK_BYTEVECTOR_DATA_CHARP(s_one); /* do something with "one" */ pcb->root0 = &s_one; { s_two = ika_bytevector_alloc(pcb, 10); } pcb->root0 = NULL; two = IK_BYTEVECTOR_DATA_CHARP(s_two); /* do something with "one" and "two" */ /* WRONG */
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.
Previous: objects memory alloc, Up: objects memory [Index]