All Rights Reserved.
This software is provided AS-IS with no warranty, either express or
implied.
This software is distributed under license and may not be copied,
modified or distributed except as expressly authorized under the terms
of the license contained in the file LICENSE in this distribution.
Refer to licensing information at http://www.artifex.com or contact
Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
CA 94129, USA, for further information.
*/
#include "memory_.h"
#include "ghost.h"
#include "gsexit.h"
#include "gsstruct.h"
#include "iname.h"
#include "iastate.h"
#include "idebug.h"
#include "igc.h"
#include "ipacked.h"
#include "store.h"
#if 0
# define rputc(m,c) dmputc(m,c)
#else
# define rputc(m,c) DO_NOTHING
#endif
ptr_proc_reloc(igc_reloc_ref_ptr, ref_packed);
ptr_proc_reloc(igc_reloc_ref_ptr_nocheck, ref_packed);
refs_proc_reloc(igc_reloc_refs);
* Define the 'structure' type descriptor for refs.
* This is special because it has different shared procs.
*/
static gc_proc_clear_reloc(refs_clear_reloc);
static gc_proc_set_reloc(refs_set_reloc);
static gc_proc_compact(refs_compact);
static const struct_shared_procs_t refs_shared_procs =
{refs_clear_reloc, refs_set_reloc, refs_compact};
static struct_proc_clear_marks(refs_clear_marks);
static struct_proc_reloc_ptrs(refs_do_reloc);
const gs_memory_struct_type_t st_refs =
{sizeof(ref), "refs", &refs_shared_procs, refs_clear_marks, 0, refs_do_reloc};
* Define the GC procedures for structs that actually contain refs.
* These are special because the shared refs_* procedures
* are never called. Instead, we unmark the individual refs in clear_marks,
* disregard refs_*_reloc (because we will never relocate a ptr_ref_type
* pointer pointing into the structure), disregard refs_compact (because
* compaction is never required), and remove the marks in reloc_ptrs.
* See also the comment about ptr_ref_type in imemory.h.
*/
CLEAR_MARKS_PROC(ref_struct_clear_marks)
{
ref *pref = (ref *) vptr;
ref *end = (ref *) ((char *)vptr + size);
for (; pref < end; pref++)
r_clear_attrs(pref, l_mark);
}
ENUM_PTRS_BEGIN_PROC(ref_struct_enum_ptrs)
{
if (index >= size / sizeof(ref))
return 0;
pep->ptr = (const ref *)vptr + index;
return ptr_ref_type;
ENUM_PTRS_END_PROC
}
RELOC_PTRS_BEGIN(ref_struct_reloc_ptrs)
{
vm_spaces spaces = gcst->spaces;
const gs_memory_t *cmem = space_system->stable_memory;
ref *beg = vptr;
ref *end = (ref *) ((char *)vptr + size);
igc_reloc_refs((ref_packed *) beg, (ref_packed *) end, gcst);
ref_struct_clear_marks(cmem, vptr, size, pstype);
} RELOC_PTRS_END
void
ptr_ref_unmark(enum_ptr_t *pep, gc_state_t * ignored)
{
ref_packed *rpp = (ref_packed *)pep->ptr;
if (r_is_packed(rpp))
r_clear_pmark(rpp);
else
r_clear_attrs((ref *)rpp, l_mark);
}
static void
refs_clear_marks(const gs_memory_t *cmem,
void *vptr, uint size,
const gs_memory_struct_type_t * pstype)
{
ref_packed *rp = (ref_packed *) vptr;
ref_packed *end = (ref_packed *) ((byte *) vptr + size);
for (;;) {
if (r_is_packed(rp)) {
#ifdef DEBUG
if (gs_debug_c('8')) {
dmlprintf1(cmem, " [8]unmark packed "PRI_INTPTR" ", (intptr_t) rp);
debug_print_ref(cmem, (const ref *)rp);
dmputs(cmem, "\n");
}
#endif
r_clear_pmark(rp);
rp++;
} else {
ref *const pref = (ref *)rp;
#ifdef DEBUG
if (gs_debug_c('8')) {
dmlprintf1(cmem, " [8]unmark ref "PRI_INTPTR" ", (intptr_t)rp);
debug_print_ref(cmem, pref);
dmputs(cmem, "\n");
}
#endif
r_clear_attrs(pref, l_mark);
rp += packed_per_ref;
if (rp >= (ref_packed *) end)
break;
}
}
}
bool
ptr_ref_mark(enum_ptr_t *pep, gc_state_t * ignored)
{
ref_packed *rpp = (void *)pep->ptr;
if (r_is_packed(rpp)) {
if (r_has_pmark(rpp))
return false;
r_set_pmark(rpp);
} else {
ref *const pref = (ref *)rpp;
if (r_has_attr(pref, l_mark))
return false;
r_set_attrs(pref, l_mark);
}
return true;
}
* We store relocation in the size field of refs that don't use it,
* so that we don't have to scan all the way to an unmarked object.
* We must avoid nulls, which sometimes have useful information
* in their size fields, and the types above t_next_index, which are
* actually operators in disguise and also use the size field.
*/
static void
refs_clear_reloc(obj_header_t *hdr, uint size)
{
ref_packed *rp = (ref_packed *) (hdr + 1);
ref_packed *end = (ref_packed *) ((byte *) rp + size);
while (rp < end) {
if (r_is_packed(rp))
rp++;
else {
ref *const pref = (ref *)rp;
if (!ref_type_uses_size_or_null(r_type(pref))) {
if_debug1('8', " [8]clearing reloc at "PRI_INTPTR"\n", (intptr_t)rp);
r_set_size(pref, 0);
}
rp += packed_per_ref;
}
}
}
static bool
refs_set_reloc(obj_header_t * hdr, uint reloc, uint size)
{
ref_packed *rp = (ref_packed *) (hdr + 1);
ref_packed *end = (ref_packed *) ((byte *) rp + size);
uint freed = 0;
* We have to be careful to keep refs aligned properly.
* For the moment, we do this by either keeping or discarding
* an entire (aligned) block of align_packed_per_ref packed elements
* as a unit. We know that align_packed_per_ref <= packed_per_ref,
* and we also know that packed refs are always allocated in blocks
* of align_packed_per_ref, so this makes things relatively easy.
*/
while (rp < end) {
if (r_is_packed(rp)) {
#if align_packed_per_ref == 1
if (r_has_pmark(rp)) {
if_debug1('8',
" [8]packed ref "PRI_INTPTR" is marked\n",
(intptr_t)rp);
rp++;
} else {
#else
int i;
* Note: align_packed_per_ref is typically
* 2 or 4 for 32-bit processors.
*/
#define all_marked (align_packed_per_ref * lp_mark)
# if align_packed_per_ref == 2
# if ARCH_SIZEOF_INT == ARCH_SIZEOF_SHORT * 2
# undef all_marked
# define all_marked ( (lp_mark << (sizeof(short) * 8)) + lp_mark )
# define marked (*(int *)rp & all_marked)
# else
# define marked ((*rp & lp_mark) + (rp[1] & lp_mark))
# endif
# else
# if align_packed_per_ref == 4
# define marked ((*rp & lp_mark) + (rp[1] & lp_mark) +\
(rp[2] & lp_mark) + (rp[3] & lp_mark))
# else
* The value of marked is logically a uint, not an int:
* we declare it as int only to avoid a compiler warning
* message about using a non-int value in a switch statement.
*/
int marked = *rp & lp_mark;
for (i = 1; i < align_packed_per_ref; i++)
marked += rp[i] & lp_mark;
# endif
# endif
* Now marked is lp_mark * the number of marked
* packed refs in the aligned block, except for
* a couple of special cases above.
*/
switch (marked) {
case all_marked:
if_debug2('8',
" [8]packed refs "PRI_INTPTR".."PRI_INTPTR" are marked\n",
(intptr_t)rp,
(intptr_t)(rp + (align_packed_per_ref - 1)));
rp += align_packed_per_ref;
break;
default:
for (i = align_packed_per_ref; i--; rp++) {
r_set_pmark(rp);
if_debug1('8',
" [8]packed ref "PRI_INTPTR" is marked\n",
(intptr_t)rp);
}
break;
case 0:
#endif
if_debug2('8', " [8]%d packed ref(s) at "PRI_INTPTR" are unmarked\n",
align_packed_per_ref, (intptr_t)rp);
{
uint rel = reloc + freed;
*rp = pt_tag(pt_integer) +
min(rel, packed_max_value);
}
rp += align_packed_per_ref;
freed += sizeof(ref_packed) * align_packed_per_ref;
}
} else {
uint rel = reloc + freed;
ref *pref = (ref *) rp;
if (!r_has_attr(pref, l_mark)) {
if_debug1('8', " [8]ref "PRI_INTPTR" is unmarked\n",
(intptr_t)pref);
r_set_type(pref, t_mark);
r_set_size(pref, rel);
freed += sizeof(ref);
} else {
if_debug1('8', " [8]ref "PRI_INTPTR" is marked\n",
(intptr_t)pref);
if (!ref_type_uses_size_or_null(r_type(pref))) {
if_debug2('8', " [8]storing reloc %u at "PRI_INTPTR"\n",
rel, (intptr_t)pref);
r_set_size(pref, rel);
}
}
rp += packed_per_ref;
}
}
if_debug3('7', " [7]at end of refs "PRI_INTPTR", size = %u, freed = %u\n",
(intptr_t)(hdr + 1), size, freed);
if (freed == size)
return false;
#if ARCH_SIZEOF_INT > ARCH_SIZEOF_SHORT
* If the final relocation can't fit in the r_size field
* (which can't happen if the object shares a clump with
* any other objects, so we know reloc = 0 in this case),
* we have to keep the entire object unless there are no
* references to any ref in it.
*/
if (freed <= max_ushort)
return true;
* We have to mark all surviving refs, but we also must
* overwrite any non-surviving refs with something that
* doesn't contain any pointers.
*/
rp = (ref_packed *) (hdr + 1);
while (rp < end) {
if (r_is_packed(rp)) {
if (!r_has_pmark(rp))
*rp = pt_tag(pt_integer) | lp_mark;
++rp;
} else {
ref *pref = (ref *) rp;
if (!r_has_attr(pref, l_mark)) {
r_set_type_attrs(pref, t_mark, l_mark);
r_set_size(pref, reloc);
} else {
if (!ref_type_uses_size_or_null(r_type(pref)))
r_set_size(pref, reloc);
}
rp += packed_per_ref;
}
}
r_clear_attrs((ref *) rp - 1, l_mark);
#endif
return true;
}
static void
refs_do_reloc(void *vptr, uint size,
const gs_memory_struct_type_t * pstype, gc_state_t * gcst)
{
igc_reloc_refs((ref_packed *) vptr,
(ref_packed *) ((char *)vptr + size),
gcst);
}
void
igc_reloc_refs(ref_packed * from, ref_packed * to, gc_state_t * gcst)
{
int min_trace = gcst->min_collect;
ref_packed *rp = from;
bool do_all = gcst->relocating_untraced;
vm_spaces spaces = gcst->spaces;
const gs_memory_t *cmem = space_system->stable_memory;
while (rp < to) {
ref *pref;
#ifdef DEBUG
const void *before = 0;
const void *after = 0;
# define DO_RELOC(var, stat)\
BEGIN before = (var); stat; after = (var); END
# define SET_RELOC(var, expr)\
BEGIN before = (var); after = (var) = (expr); END
#else
# define DO_RELOC(var, stat) stat
# define SET_RELOC(var, expr) var = expr
#endif
if (r_is_packed(rp)) {
rp++;
continue;
}
pref = (ref *) rp;
if_debug3m('8', gcst->heap, " [8]relocating %s %d ref at "PRI_INTPTR"\n",
(r_has_attr(pref, l_mark) ? "marked" : "unmarked"),
r_btype(pref), (intptr_t)pref);
if ((r_has_attr(pref, l_mark) || do_all) &&
r_space(pref) >= min_trace
) {
switch (r_type(pref)) {
case t_file:
DO_RELOC(pref->value.pfile, RELOC_VAR(pref->value.pfile));
break;
case t_device:
DO_RELOC(pref->value.pdevice,
RELOC_VAR(pref->value.pdevice));
break;
case t_fontID:
case t_struct:
case t_astruct:
case t_pdfctx:
DO_RELOC(pref->value.pstruct,
RELOC_VAR(pref->value.pstruct));
break;
case t_dictionary:
rputc(gcst->heap, 'd');
SET_RELOC(pref->value.pdict,
(dict *)igc_reloc_ref_ptr((ref_packed *)pref->value.pdict, gcst));
break;
case t_array:
{
uint size = r_size(pref);
if (size != 0) {
* If the array is large, we allocated it in its
* own object (at least originally -- this might
* be a pointer to a subarray.) In this case,
* we know it is the only object in its
* containing st_refs object, so we know that
* the mark containing the relocation appears
* just after it.
*/
if (size < max_size_st_refs / sizeof(ref)) {
rputc(gcst->heap, 'a');
SET_RELOC(pref->value.refs,
(ref *) igc_reloc_ref_ptr(
(ref_packed *) pref->value.refs, gcst));
} else {
rputc(gcst->heap, 'A');
* See the t_shortarray case below for why we
* decrement size.
*/
--size;
SET_RELOC(pref->value.refs,
(ref *) igc_reloc_ref_ptr(
(ref_packed *) (pref->value.refs + size),
gcst) - size);
}
}
}
break;
case t_mixedarray:
if (r_size(pref) != 0) {
rputc(gcst->heap, 'm');
SET_RELOC(pref->value.packed,
igc_reloc_ref_ptr(pref->value.packed, gcst));
}
break;
case t_shortarray:
{
uint size = r_size(pref);
* Since we know that igc_reloc_ref_ptr works by
* scanning forward, and we know that all the
* elements of this array itself are marked, we can
* save some scanning time by relocating the pointer
* to the end of the array rather than the
* beginning.
*/
if (size != 0) {
rputc(gcst->heap, 's');
* igc_reloc_ref_ptr has to be able to determine
* whether the pointer points into a space that
* isn't being collected. It does this by
* checking whether the referent of the pointer
* is marked. For this reason, we have to pass
* a pointer to the last real element of the
* array, rather than just beyond it.
*/
--size;
SET_RELOC(pref->value.packed,
igc_reloc_ref_ptr(pref->value.packed + size,
gcst) - size);
}
}
break;
case t_name:
{
void *psub = name_ref_sub_table(cmem, pref);
void *rsub = RELOC_OBJ(psub);
SET_RELOC(pref->value.pname,
(name *)
((char *)rsub + ((char *)pref->value.pname -
(char *)psub)));
} break;
case t_string:
{
gs_string str;
str.data = pref->value.bytes;
str.size = r_size(pref);
DO_RELOC(str.data, RELOC_STRING_VAR(str));
pref->value.bytes = str.data;
}
break;
case t_oparray:
rputc(gcst->heap, 'o');
SET_RELOC(pref->value.const_refs,
(const ref *)igc_reloc_ref_ptr((const ref_packed *)pref->value.const_refs, gcst));
break;
default:
goto no_reloc;
}
if_debug2m('8', gcst->heap, " [8]relocated "PRI_INTPTR" => "PRI_INTPTR"\n",
(intptr_t)before, (intptr_t)after);
}
no_reloc:
rp += packed_per_ref;
}
}
ref_packed *
igc_reloc_ref_ptr_nocheck(const ref_packed * prp, gc_state_t *gcst)
{
* Search forward for relocation. This algorithm is intrinsically very
* inefficient; we hope eventually to replace it with a better one.
*/
const ref_packed *rp = prp;
uint dec = 0;
#ifdef ALIGNMENT_ALIASING_BUG
const ref *rpref;
# define RP_REF(rp) (rpref = (const ref *)rp, rpref)
#else
# define RP_REF(rp) ((const ref *)rp)
#endif
for (;;) {
if (r_is_packed(rp)) {
* Normally, an unmarked packed ref will be an
* integer whose value is the amount of relocation.
* However, the relocation value might have been
* too large to fit. If this is the case, for
* each such unmarked packed ref we pass over,
* we have to decrement the final relocation.
*/
rputc(gcst->heap, (*rp & lp_mark ? '1' : '0'));
if (!(*rp & lp_mark)) {
if (*rp != pt_tag(pt_integer) + packed_max_value) {
rputc(gcst->heap, '\n');
rp = print_reloc(prp, "ref",
(const ref_packed *)
((const char *)prp -
(*rp & packed_value_mask) + dec));
break;
}
* We know this is the first of an aligned block
* of packed refs. Skip over the entire block,
* decrementing the final relocation.
*/
dec += sizeof(ref_packed) * align_packed_per_ref;
rp += align_packed_per_ref;
} else
rp++;
continue;
}
if (!ref_type_uses_size_or_null(r_type(RP_REF(rp)))) {
rputc(gcst->heap, '\n');
rp = print_reloc(prp, "ref",
(const ref_packed *)
(r_size(RP_REF(rp)) == 0 ? prp :
(const ref_packed *)((const char *)prp -
r_size(RP_REF(rp)) + dec)));
break;
}
rputc(gcst->heap, 'u');
rp += packed_per_ref;
}
{
union { const ref_packed *r; ref_packed *w; } u;
u.r = rp;
return u.w;
}
#undef RP_REF
}
ref_packed *
igc_reloc_ref_ptr(const ref_packed * prp, gc_state_t *gcst)
{
* Search forward for relocation. This algorithm is intrinsically very
* inefficient; we hope eventually to replace it with a better one.
*/
const ref_packed *rp = prp;
#ifdef ALIGNMENT_ALIASING_BUG
const ref *rpref;
# define RP_REF(rp) (rpref = (const ref *)rp, rpref)
#else
# define RP_REF(rp) ((const ref *)rp)
#endif
* Iff this pointer points into a space that wasn't traced,
* the referent won't be marked. In this case, we shouldn't
* do any relocation. Check for this first.
*/
if (r_is_packed(rp)) {
if (!r_has_pmark(rp))
goto ret_rp;
} else {
if (!r_has_attr(RP_REF(rp), l_mark))
goto ret_rp;
}
return igc_reloc_ref_ptr_nocheck(prp, gcst);
ret_rp:
{
union { const ref_packed *r; ref_packed *w; } u;
u.r = rp;
return u.w;
}
}
static void
refs_compact(const gs_memory_t *mem, obj_header_t * pre, obj_header_t * dpre, uint size)
{
ref_packed *dest;
ref_packed *src;
ref_packed *end;
uint new_size;
for the loop termination condition.
It was useful during the development,
when some assumptions were temporary wrong.
We keep it for records. */
src = (ref_packed *) (pre + 1);
end = (ref_packed *) ((byte *) src + size);
* We know that a block of refs always ends with a
* full-size ref, so we only need to check for reaching the end
* of the block when we see one of those.
*/
if (dpre == pre)
for (;;) {
if (r_is_packed(src)) {
if (!r_has_pmark(src))
break;
if_debug1m('8', mem, " [8]packed ref "PRI_INTPTR" \"copied\"\n",
(intptr_t)src);
*src &= ~lp_mark;
src++;
} else {
ref *const pref = (ref *)src;
if (!r_has_attr(pref, l_mark))
break;
if_debug1m('8', mem, " [8]ref "PRI_INTPTR" \"copied\"\n", (intptr_t)src);
r_clear_attrs(pref, l_mark);
src += packed_per_ref;
}
} else
*dpre = *pre;
dest = (ref_packed *) ((char *)dpre + ((char *)src - (char *)pre));
for (;;) {
if (r_is_packed(src)) {
if (r_has_pmark(src)) {
if_debug2m('8', mem, " [8]packed ref "PRI_INTPTR" copied to "PRI_INTPTR"\n",
(intptr_t)src, (intptr_t)dest);
*dest++ = *src & ~lp_mark;
}
src++;
} else {
if (r_has_attr((ref *) src, l_mark)) {
ref rtemp;
if_debug2m('8', mem, " [8]ref "PRI_INTPTR" copied to "PRI_INTPTR"\n",
(intptr_t)src, (intptr_t)dest);
ref_assign_inline(&rtemp, (ref *) src);
r_clear_attrs(&rtemp, l_mark);
ref_assign_inline((ref *) dest, &rtemp);
src += packed_per_ref;
dest += packed_per_ref;
} else {
src += packed_per_ref;
if (src >= end)
break;
}
}
}
new_size = (byte *) dest - (byte *) (dpre + 1) + sizeof(ref);
#ifdef DEBUG
if ((byte *) src - (byte *) dest != r_size((ref *) src - 1) + sizeof(ref)) {
mlprintf3(mem, "Reloc error for refs "PRI_INTPTR": reloc = %lu, stored = %u\n",
(intptr_t) dpre, (ulong) ((byte *) src - (byte *) dest),
(uint) r_size((ref *) src - 1));
gs_abort(mem);
}
#endif
while (new_size % sizeof(ref))
*dest++ = pt_tag(pt_integer),
new_size += sizeof(ref_packed);
if (size - new_size < sizeof(obj_header_t)) {
while (new_size < size)
*dest++ = pt_tag(pt_integer),
new_size += sizeof(ref_packed);
} else {
obj_header_t *pfree = (obj_header_t *) ((ref *) dest + 1);
pfree->o_pad = 0;
pfree->o_alone = 0;
pfree->o_size = size - new_size - sizeof(obj_header_t);
pfree->o_type = &st_bytes;
}
r_set_type((ref *) dest, t_integer);
dpre->o_size = new_size;
}