1 /* Copyright (C) 2001-2012 Artifex Software, Inc.
2    All Rights Reserved.
3 
4    This software is provided AS-IS with no warranty, either express or
5    implied.
6 
7    This software is distributed under license and may not be copied,
8    modified or distributed except as expressly authorized under the terms
9    of the license contained in the file LICENSE in this distribution.
10 
11    Refer to licensing information at http://www.artifex.com or contact
12    Artifex Software, Inc.,  7 Mt. Lassen Drive - Suite A-134, San Rafael,
13    CA  94903, U.S.A., +1(415)492-9861, for further information.
14 */
15 
16 
17 /* ref garbage collector for Ghostscript */
18 #include "memory_.h"
19 #include "ghost.h"
20 #include "gsexit.h"
21 #include "gsstruct.h"		/* for gxalloc.h included by iastate.h */
22 #include "iname.h"
23 #include "iastate.h"
24 #include "idebug.h"
25 #include "igc.h"
26 #include "ipacked.h"
27 #include "store.h"		/* for ref_assign_inline */
28 
29 /* Define whether to trace every step of relocating ref pointers. */
30 #if 0
31 #  define rputc(c) dputc(c)
32 #else
33 #  define rputc(c) DO_NOTHING
34 #endif
35 
36 /* Forward references */
37 ptr_proc_reloc(igc_reloc_ref_ptr, ref_packed);
38 ptr_proc_reloc(igc_reloc_ref_ptr_nocheck, ref_packed);
39 refs_proc_reloc(igc_reloc_refs);
40 
41 /*
42  * Define the 'structure' type descriptor for refs.
43  * This is special because it has different shared procs.
44  */
45 static gc_proc_clear_reloc(refs_clear_reloc);
46 static gc_proc_set_reloc(refs_set_reloc);
47 static gc_proc_compact(refs_compact);
48 static const struct_shared_procs_t refs_shared_procs =
49 {refs_clear_reloc, refs_set_reloc, refs_compact};
50 static struct_proc_clear_marks(refs_clear_marks);
51 static struct_proc_reloc_ptrs(refs_do_reloc);
52 const gs_memory_struct_type_t st_refs =
53 {sizeof(ref), "refs", &refs_shared_procs, refs_clear_marks, 0, refs_do_reloc};
54 
55 /*
56  * Define the GC procedures for structs that actually contain refs.
57  * These are special because the shared refs_* procedures
58  * are never called.  Instead, we unmark the individual refs in clear_marks,
59  * disregard refs_*_reloc (because we will never relocate a ptr_ref_type
60  * pointer pointing into the structure), disregard refs_compact (because
61  * compaction is never required), and remove the marks in reloc_ptrs.
62  * See also the comment about ptr_ref_type in imemory.h.
63  */
CLEAR_MARKS_PROC(ref_struct_clear_marks)64 CLEAR_MARKS_PROC(ref_struct_clear_marks)
65 {
66     ref *pref = (ref *) vptr;
67     ref *end = (ref *) ((char *)vptr + size);
68 
69     for (; pref < end; pref++)
70         r_clear_attrs(pref, l_mark);
71 }
ENUM_PTRS_BEGIN_PROC(ref_struct_enum_ptrs)72 ENUM_PTRS_BEGIN_PROC(ref_struct_enum_ptrs)
73 {
74     if (index >= size / sizeof(ref))
75         return 0;
76     pep->ptr = (const ref *)vptr + index;
77     return ptr_ref_type;
78     ENUM_PTRS_END_PROC
79 }
RELOC_PTRS_BEGIN(ref_struct_reloc_ptrs)80 RELOC_PTRS_BEGIN(ref_struct_reloc_ptrs)
81 {
82     vm_spaces spaces = gcst->spaces;
83     const gs_memory_t *cmem = space_system->stable_memory;
84 
85     ref *beg = vptr;
86     ref *end = (ref *) ((char *)vptr + size);
87 
88     igc_reloc_refs((ref_packed *) beg, (ref_packed *) end, gcst);
89     ref_struct_clear_marks(cmem, vptr, size, pstype);
90 } RELOC_PTRS_END
91 
92 /* ------ Unmarking phase ------ */
93 
94 /* Unmark a single ref. */
95 void
ptr_ref_unmark(enum_ptr_t * pep,gc_state_t * ignored)96 ptr_ref_unmark(enum_ptr_t *pep, gc_state_t * ignored)
97 {
98     ref_packed *rpp = (ref_packed *)pep->ptr;
99 
100     if (r_is_packed(rpp))
101         r_clear_pmark(rpp);
102     else
103         r_clear_attrs((ref *)rpp, l_mark);
104 }
105 
106 /* Unmarking routine for ref objects. */
107 static void
refs_clear_marks(const gs_memory_t * cmem,void * vptr,uint size,const gs_memory_struct_type_t * pstype)108 refs_clear_marks(const gs_memory_t *cmem,
109                  void /*obj_header_t */ *vptr, uint size,
110                  const gs_memory_struct_type_t * pstype)
111 {
112     ref_packed *rp = (ref_packed *) vptr;
113     ref_packed *end = (ref_packed *) ((byte *) vptr + size);
114 
115     /* Since the last ref is full-size, we only need to check for */
116     /* the end of the block when we see one of those. */
117     for (;;) {
118         if (r_is_packed(rp)) {
119 #ifdef DEBUG
120             if (gs_debug_c('8')) {
121                 dlprintf1("  [8]unmark packed 0x%lx ", (ulong) rp);
122                 debug_print_ref(cmem, (const ref *)rp);
123                 dputs("\n");
124             }
125 #endif
126             r_clear_pmark(rp);
127             rp++;
128         } else {		/* full-size ref */
129             ref *const pref = (ref *)rp;
130 
131 #ifdef DEBUG
132             if (gs_debug_c('8')) {
133                 dlprintf1("  [8]unmark ref 0x%lx ", (ulong) rp);
134                 debug_print_ref(cmem, pref);
135                 dputs("\n");
136             }
137 #endif
138             r_clear_attrs(pref, l_mark);
139             rp += packed_per_ref;
140             if (rp >= (ref_packed *) end)
141                 break;
142         }
143     }
144 }
145 
146 /* ------ Marking phase ------ */
147 
148 /* Mark a ref.  Return true if new mark. */
149 bool
ptr_ref_mark(enum_ptr_t * pep,gc_state_t * ignored)150 ptr_ref_mark(enum_ptr_t *pep, gc_state_t * ignored)
151 {
152     ref_packed *rpp = (void *)pep->ptr;
153 
154     if (r_is_packed(rpp)) {
155         if (r_has_pmark(rpp))
156             return false;
157         r_set_pmark(rpp);
158     } else {
159         ref *const pref = (ref *)rpp;
160 
161         if (r_has_attr(pref, l_mark))
162             return false;
163         r_set_attrs(pref, l_mark);
164     }
165     return true;
166 }
167 
168 /* ------ Relocation planning phase ------ */
169 
170 /*
171  * We store relocation in the size field of refs that don't use it,
172  * so that we don't have to scan all the way to an unmarked object.
173  * We must avoid nulls, which sometimes have useful information
174  * in their size fields, and the types above t_next_index, which are
175  * actually operators in disguise and also use the size field.
176  */
177 
178 /* Clear the relocation for a ref object. */
179 static void
refs_clear_reloc(obj_header_t * hdr,uint size)180 refs_clear_reloc(obj_header_t *hdr, uint size)
181 {
182     ref_packed *rp = (ref_packed *) (hdr + 1);
183     ref_packed *end = (ref_packed *) ((byte *) rp + size);
184 
185     while (rp < end) {
186         if (r_is_packed(rp))
187             rp++;
188         else {
189             /* Full-size ref.  Store the relocation here if possible. */
190             ref *const pref = (ref *)rp;
191 
192             if (!ref_type_uses_size_or_null(r_type(pref))) {
193                 if_debug1('8', "  [8]clearing reloc at 0x%lx\n", (ulong) rp);
194                 r_set_size(pref, 0);
195             }
196             rp += packed_per_ref;
197         }
198     }
199 }
200 
201 /* Set the relocation for a ref object. */
202 static bool
refs_set_reloc(obj_header_t * hdr,uint reloc,uint size)203 refs_set_reloc(obj_header_t * hdr, uint reloc, uint size)
204 {
205     ref_packed *rp = (ref_packed *) (hdr + 1);
206     ref_packed *end = (ref_packed *) ((byte *) rp + size);
207     uint freed = 0;
208 
209     /*
210      * We have to be careful to keep refs aligned properly.
211      * For the moment, we do this by either keeping or discarding
212      * an entire (aligned) block of align_packed_per_ref packed elements
213      * as a unit.  We know that align_packed_per_ref <= packed_per_ref,
214      * and we also know that packed refs are always allocated in blocks
215      * of align_packed_per_ref, so this makes things relatively easy.
216      */
217     while (rp < end) {
218         if (r_is_packed(rp)) {
219 #if align_packed_per_ref == 1
220             if (r_has_pmark(rp)) {
221                 if_debug1('8',
222                           "  [8]packed ref 0x%lx is marked\n",
223                           (ulong) rp);
224                 rp++;
225             } else {
226 #else
227             int i;
228 
229             /*
230              * Note: align_packed_per_ref is typically
231              * 2 or 4 for 32-bit processors.
232              */
233 #define all_marked (align_packed_per_ref * lp_mark)
234 # if align_packed_per_ref == 2
235 #  if arch_sizeof_int == arch_sizeof_short * 2
236 #    undef all_marked
237 #    define all_marked ( (lp_mark << (sizeof(short) * 8)) + lp_mark )
238 #    define marked (*(int *)rp & all_marked)
239 #  else
240 #    define marked ((*rp & lp_mark) + (rp[1] & lp_mark))
241 #  endif
242 # else
243 #  if align_packed_per_ref == 4
244 #    define marked ((*rp & lp_mark) + (rp[1] & lp_mark) +\
245                     (rp[2] & lp_mark) + (rp[3] & lp_mark))
246 #  else
247             /*
248              * The value of marked is logically a uint, not an int:
249              * we declare it as int only to avoid a compiler warning
250              * message about using a non-int value in a switch statement.
251              */
252             int marked = *rp & lp_mark;
253 
254             for (i = 1; i < align_packed_per_ref; i++)
255                 marked += rp[i] & lp_mark;
256 #  endif
257 # endif
258             /*
259              * Now marked is lp_mark * the number of marked
260              * packed refs in the aligned block, except for
261              * a couple of special cases above.
262              */
263             switch (marked) {
264                 case all_marked:
265                     if_debug2('8',
266                               "  [8]packed refs 0x%lx..0x%lx are marked\n",
267                               (ulong) rp,
268                               (ulong) (rp + (align_packed_per_ref - 1)));
269                     rp += align_packed_per_ref;
270                     break;
271                 default:
272                     /* At least one packed ref in the block */
273                     /* is marked: Keep the whole block. */
274                     for (i = align_packed_per_ref; i--; rp++) {
275                         r_set_pmark(rp);
276                         if_debug1('8',
277                                   "  [8]packed ref 0x%lx is marked\n",
278                                   (ulong) rp);
279                     }
280                     break;
281                 case 0:
282 #endif
283                     if_debug2('8', "  [8]%d packed ref(s) at 0x%lx are unmarked\n",
284                               align_packed_per_ref, (ulong) rp);
285                     {
286                         uint rel = reloc + freed;
287 
288                         /* Change this to an integer so we can */
289                         /* store the relocation here. */
290                         *rp = pt_tag(pt_integer) +
291                             min(rel, packed_max_value);
292                     }
293                     rp += align_packed_per_ref;
294                     freed += sizeof(ref_packed) * align_packed_per_ref;
295             }
296         } else {		/* full-size ref */
297             uint rel = reloc + freed;
298 
299             /* The following assignment is logically */
300             /* unnecessary; we do it only for convenience */
301             /* in debugging. */
302             ref *pref = (ref *) rp;
303 
304             if (!r_has_attr(pref, l_mark)) {
305                 if_debug1('8', "  [8]ref 0x%lx is unmarked\n",
306                           (ulong) pref);
307                 /* Change this to a mark so we can */
308                 /* store the relocation. */
309                 r_set_type(pref, t_mark);
310                 r_set_size(pref, rel);
311                 freed += sizeof(ref);
312             } else {
313                 if_debug1('8', "  [8]ref 0x%lx is marked\n",
314                           (ulong) pref);
315                 /* Store the relocation here if possible. */
316                 if (!ref_type_uses_size_or_null(r_type(pref))) {
317                     if_debug2('8', "  [8]storing reloc %u at 0x%lx\n",
318                               rel, (ulong) pref);
319                     r_set_size(pref, rel);
320                 }
321             }
322             rp += packed_per_ref;
323         }
324     }
325     if_debug3('7', " [7]at end of refs 0x%lx, size = %u, freed = %u\n",
326               (ulong) (hdr + 1), size, freed);
327     if (freed == size)
328         return false;
329 #if arch_sizeof_int > arch_sizeof_short
330     /*
331      * If the final relocation can't fit in the r_size field
332      * (which can't happen if the object shares a chunk with
333      * any other objects, so we know reloc = 0 in this case),
334      * we have to keep the entire object unless there are no
335      * references to any ref in it.
336      */
337     if (freed <= max_ushort)
338         return true;
339     /*
340      * We have to mark all surviving refs, but we also must
341      * overwrite any non-surviving refs with something that
342      * doesn't contain any pointers.
343      */
344     rp = (ref_packed *) (hdr + 1);
345     while (rp < end) {
346         if (r_is_packed(rp)) {
347             if (!r_has_pmark(rp))
348                 *rp = pt_tag(pt_integer) | lp_mark;
349             ++rp;
350         } else {		/* The following assignment is logically */
351             /* unnecessary; we do it only for convenience */
352             /* in debugging. */
353             ref *pref = (ref *) rp;
354 
355             if (!r_has_attr(pref, l_mark)) {
356                 r_set_type_attrs(pref, t_mark, l_mark);
357                 r_set_size(pref, reloc);
358             } else {
359                 if (!ref_type_uses_size_or_null(r_type(pref)))
360                     r_set_size(pref, reloc);
361             }
362             rp += packed_per_ref;
363         }
364     }
365     /* The last ref has to remain unmarked. */
366     r_clear_attrs((ref *) rp - 1, l_mark);
367 #endif
368     return true;
369 }
370 
371 /* ------ Relocation phase ------ */
372 
373 /* Relocate all the pointers in a block of refs. */
374 static void
375 refs_do_reloc(void /*obj_header_t */ *vptr, uint size,
376               const gs_memory_struct_type_t * pstype, gc_state_t * gcst)
377 {
378     igc_reloc_refs((ref_packed *) vptr,
379                    (ref_packed *) ((char *)vptr + size),
380                    gcst);
381 }
382 /* Relocate the contents of a block of refs. */
383 /* If gcst->relocating_untraced is true, we are relocating pointers from an */
384 /* untraced space, so relocate all refs, not just marked ones. */
385 void
386 igc_reloc_refs(ref_packed * from, ref_packed * to, gc_state_t * gcst)
387 {
388     int min_trace = gcst->min_collect;
389     ref_packed *rp = from;
390     bool do_all = gcst->relocating_untraced;
391 
392     vm_spaces spaces = gcst->spaces;
393     const gs_memory_t *cmem = space_system->stable_memory;
394 
395     while (rp < to) {
396         ref *pref;
397 #ifdef DEBUG
398         const void *before = 0;
399         const void *after = 0;
400 # define DO_RELOC(var, stat)\
401     BEGIN before = (var); stat; after = (var); END
402 # define SET_RELOC(var, expr)\
403     BEGIN before = (var); after = (var) = (expr); END
404 #else
405 # define DO_RELOC(var, stat) stat
406 # define SET_RELOC(var, expr) var = expr
407 #endif
408 
409         if (r_is_packed(rp)) {
410             rp++;
411             continue;
412         }
413         /* The following assignment is logically unnecessary; */
414         /* we do it only for convenience in debugging. */
415         pref = (ref *) rp;
416         if_debug3('8', "  [8]relocating %s %d ref at 0x%lx\n",
417                   (r_has_attr(pref, l_mark) ? "marked" : "unmarked"),
418                   r_btype(pref), (ulong) pref);
419         if ((r_has_attr(pref, l_mark) || do_all) &&
420             r_space(pref) >= min_trace
421             ) {
422             switch (r_type(pref)) {
423                     /* Struct cases */
424                 case t_file:
425                     DO_RELOC(pref->value.pfile, RELOC_VAR(pref->value.pfile));
426                     break;
427                 case t_device:
428                     DO_RELOC(pref->value.pdevice,
429                              RELOC_VAR(pref->value.pdevice));
430                     break;
431                 case t_fontID:
432                 case t_struct:
433                 case t_astruct:
434                     DO_RELOC(pref->value.pstruct,
435                              RELOC_VAR(pref->value.pstruct));
436                     break;
437                     /* Non-trivial non-struct cases */
438                 case t_dictionary:
439                     rputc('d');
440                     SET_RELOC(pref->value.pdict,
441                               (dict *)igc_reloc_ref_ptr((ref_packed *)pref->value.pdict, gcst));
442                     break;
443                 case t_array:
444                     {
445                         uint size = r_size(pref);
446 
447                         if (size != 0) {	/* value.refs might be NULL */
448 
449                             /*
450                              * If the array is large, we allocated it in its
451                              * own object (at least originally -- this might
452                              * be a pointer to a subarray.)  In this case,
453                              * we know it is the only object in its
454                              * containing st_refs object, so we know that
455                              * the mark containing the relocation appears
456                              * just after it.
457                              */
458                             if (size < max_size_st_refs / sizeof(ref)) {
459                                 rputc('a');
460                                 SET_RELOC(pref->value.refs,
461                                     (ref *) igc_reloc_ref_ptr(
462                                      (ref_packed *) pref->value.refs, gcst));
463                             } else {
464                                 rputc('A');
465                                 /*
466                                  * See the t_shortarray case below for why we
467                                  * decrement size.
468                                  */
469                                 --size;
470                                 SET_RELOC(pref->value.refs,
471                                     (ref *) igc_reloc_ref_ptr(
472                                    (ref_packed *) (pref->value.refs + size),
473                                                                gcst) - size);
474                             }
475                         }
476                     }
477                     break;
478                 case t_mixedarray:
479                     if (r_size(pref) != 0) {	/* value.refs might be NULL */
480                         rputc('m');
481                         SET_RELOC(pref->value.packed,
482                                   igc_reloc_ref_ptr(pref->value.packed, gcst));
483                     }
484                     break;
485                 case t_shortarray:
486                     {
487                         uint size = r_size(pref);
488 
489                         /*
490                          * Since we know that igc_reloc_ref_ptr works by
491                          * scanning forward, and we know that all the
492                          * elements of this array itself are marked, we can
493                          * save some scanning time by relocating the pointer
494                          * to the end of the array rather than the
495                          * beginning.
496                          */
497                         if (size != 0) {	/* value.refs might be NULL */
498                             rputc('s');
499                             /*
500                              * igc_reloc_ref_ptr has to be able to determine
501                              * whether the pointer points into a space that
502                              * isn't being collected.  It does this by
503                              * checking whether the referent of the pointer
504                              * is marked.  For this reason, we have to pass
505                              * a pointer to the last real element of the
506                              * array, rather than just beyond it.
507                              */
508                             --size;
509                             SET_RELOC(pref->value.packed,
510                                 igc_reloc_ref_ptr(pref->value.packed + size,
511                                                   gcst) - size);
512                         }
513                     }
514                     break;
515                 case t_name:
516                     {
517                         void *psub = name_ref_sub_table(cmem, pref);
518                         void *rsub = RELOC_OBJ(psub); /* gcst implicit */
519 
520                         SET_RELOC(pref->value.pname,
521                                   (name *)
522                                   ((char *)rsub + ((char *)pref->value.pname -
523                                                    (char *)psub)));
524                     } break;
525                 case t_string:
526                     {
527                         gs_string str;
528 
529                         str.data = pref->value.bytes;
530                         str.size = r_size(pref);
531 
532                         DO_RELOC(str.data, RELOC_STRING_VAR(str));
533                         pref->value.bytes = str.data;
534                     }
535                     break;
536                 case t_oparray:
537                     rputc('o');
538                     SET_RELOC(pref->value.const_refs,
539                         (const ref *)igc_reloc_ref_ptr((const ref_packed *)pref->value.const_refs, gcst));
540                     break;
541                 default:
542                     goto no_reloc; /* don't print trace message */
543             }
544             if_debug2('8', "  [8]relocated 0x%lx => 0x%lx\n",
545                       (ulong)before, (ulong)after);
546         }
547 no_reloc:
548         rp += packed_per_ref;
549     }
550 }
551 
552 /* Relocate a pointer to a ref. */
553 /* See gsmemory.h for why the argument is const and the result is not. */
554 ref_packed *
555 igc_reloc_ref_ptr_nocheck(const ref_packed * prp, gc_state_t *gcst)
556 {
557     /*
558      * Search forward for relocation.  This algorithm is intrinsically very
559      * inefficient; we hope eventually to replace it with a better one.
560      */
561     const ref_packed *rp = prp;
562     uint dec = 0;
563 #ifdef ALIGNMENT_ALIASING_BUG
564     const ref *rpref;
565 # define RP_REF(rp) (rpref = (const ref *)rp, rpref)
566 #else
567 # define RP_REF(rp) ((const ref *)rp)
568 #endif
569     for (;;) {
570 
571         if (r_is_packed(rp)) {
572             /*
573              * Normally, an unmarked packed ref will be an
574              * integer whose value is the amount of relocation.
575              * However, the relocation value might have been
576              * too large to fit.  If this is the case, for
577              * each such unmarked packed ref we pass over,
578              * we have to decrement the final relocation.
579              */
580             rputc((*rp & lp_mark ? '1' : '0'));
581             if (!(*rp & lp_mark)) {
582                 if (*rp != pt_tag(pt_integer) + packed_max_value) {
583                     /* This is a stored relocation value. */
584                     rputc('\n');
585                     rp = print_reloc(prp, "ref",
586                                      (const ref_packed *)
587                                      ((const char *)prp -
588                                       (*rp & packed_value_mask) + dec));
589                     break;
590                 }
591                 /*
592                  * We know this is the first of an aligned block
593                  * of packed refs.  Skip over the entire block,
594                  * decrementing the final relocation.
595                  */
596                 dec += sizeof(ref_packed) * align_packed_per_ref;
597                 rp += align_packed_per_ref;
598             } else
599                 rp++;
600             continue;
601         }
602         if (!ref_type_uses_size_or_null(r_type(RP_REF(rp)))) {
603             /* reloc is in r_size */
604             rputc('\n');
605             rp = print_reloc(prp, "ref",
606                              (const ref_packed *)
607                              (r_size(RP_REF(rp)) == 0 ? prp :
608                               (const ref_packed *)((const char *)prp -
609                                                    r_size(RP_REF(rp)) + dec)));
610             break;
611         }
612         rputc('u');
613         rp += packed_per_ref;
614     }
615     /* Use a severely deprecated pun to remove the const property. */
616     {
617         union { const ref_packed *r; ref_packed *w; } u;
618 
619         u.r = rp;
620         return u.w;
621     }
622 #undef RP_REF
623 }
624 ref_packed *
625 igc_reloc_ref_ptr(const ref_packed * prp, gc_state_t *gcst)
626 {
627     /*
628      * Search forward for relocation.  This algorithm is intrinsically very
629      * inefficient; we hope eventually to replace it with a better one.
630      */
631     const ref_packed *rp = prp;
632 #ifdef ALIGNMENT_ALIASING_BUG
633     const ref *rpref;
634 # define RP_REF(rp) (rpref = (const ref *)rp, rpref)
635 #else
636 # define RP_REF(rp) ((const ref *)rp)
637 #endif
638     /*
639      * Iff this pointer points into a space that wasn't traced,
640      * the referent won't be marked.  In this case, we shouldn't
641      * do any relocation.  Check for this first.
642      */
643     if (r_is_packed(rp)) {
644         if (!r_has_pmark(rp))
645             goto ret_rp;
646     } else {
647         if (!r_has_attr(RP_REF(rp), l_mark))
648             goto ret_rp;
649     }
650     return igc_reloc_ref_ptr_nocheck(prp, gcst);
651 ret_rp:
652     /* Use a severely deprecated pun to remove the const property. */
653     {
654         union { const ref_packed *r; ref_packed *w; } u;
655 
656         u.r = rp;
657         return u.w;
658     }
659 }
660 
661 /* ------ Compaction phase ------ */
662 
663 /* Compact a ref object. */
664 /* Remove the marks at the same time. */
665 static void
666 refs_compact(const gs_memory_t *mem, obj_header_t * pre, obj_header_t * dpre, uint size)
667 {
668     ref_packed *dest;
669     ref_packed *src;
670     ref_packed *end;
671     uint new_size;
672 
673    /* The next switch controls an optimization
674       for the loop termination condition.
675       It was useful during the development,
676       when some assumptions were temporary wrong.
677       We keep it for records. */
678 
679     src = (ref_packed *) (pre + 1);
680     end = (ref_packed *) ((byte *) src + size);
681     /*
682      * We know that a block of refs always ends with a
683      * full-size ref, so we only need to check for reaching the end
684      * of the block when we see one of those.
685      */
686     if (dpre == pre)		/* Loop while we don't need to copy. */
687         for (;;) {
688             if (r_is_packed(src)) {
689                 if (!r_has_pmark(src))
690                     break;
691                 if_debug1('8', "  [8]packed ref 0x%lx \"copied\"\n",
692                           (ulong) src);
693                 *src &= ~lp_mark;
694                 src++;
695             } else {		/* full-size ref */
696                 ref *const pref = (ref *)src;
697 
698                 if (!r_has_attr(pref, l_mark))
699                     break;
700                 if_debug1('8', "  [8]ref 0x%lx \"copied\"\n", (ulong) src);
701                 r_clear_attrs(pref, l_mark);
702                 src += packed_per_ref;
703             }
704     } else
705         *dpre = *pre;
706     dest = (ref_packed *) ((char *)dpre + ((char *)src - (char *)pre));
707     for (;;) {
708         if (r_is_packed(src)) {
709             if (r_has_pmark(src)) {
710                 if_debug2('8', "  [8]packed ref 0x%lx copied to 0x%lx\n",
711                           (ulong) src, (ulong) dest);
712                 *dest++ = *src & ~lp_mark;
713             }
714             src++;
715         } else {		/* full-size ref */
716             if (r_has_attr((ref *) src, l_mark)) {
717                 ref rtemp;
718 
719                 if_debug2('8', "  [8]ref 0x%lx copied to 0x%lx\n",
720                           (ulong) src, (ulong) dest);
721                 /* We can't just use ref_assign_inline, */
722                 /* because the source and destination */
723                 /* might overlap! */
724                 ref_assign_inline(&rtemp, (ref *) src);
725                 r_clear_attrs(&rtemp, l_mark);
726                 ref_assign_inline((ref *) dest, &rtemp);
727                 src += packed_per_ref;
728                 dest += packed_per_ref;
729             } else {		/* check for end of block */
730                 src += packed_per_ref;
731                 if (src >= end)
732                     break;
733             }
734         }
735     }
736     new_size = (byte *) dest - (byte *) (dpre + 1) + sizeof(ref);
737 #ifdef DEBUG
738     /* Check that the relocation came out OK. */
739     /* NOTE: this check only works within a single chunk. */
740     if ((byte *) src - (byte *) dest != r_size((ref *) src - 1) + sizeof(ref)) {
741         lprintf3("Reloc error for refs 0x%lx: reloc = %lu, stored = %u\n",
742                  (ulong) dpre, (ulong) ((byte *) src - (byte *) dest),
743                  (uint) r_size((ref *) src - 1));
744         gs_abort(mem);
745     }
746 #endif
747     /* Pad to a multiple of sizeof(ref). */
748     while (new_size & (sizeof(ref) - 1))
749         *dest++ = pt_tag(pt_integer),
750             new_size += sizeof(ref_packed);
751     /* We want to make the newly freed space into a free block, */
752     /* but we can only do this if we have enough room. */
753     if (size - new_size < sizeof(obj_header_t)) {	/* Not enough room.  Pad to original size. */
754         while (new_size < size)
755             *dest++ = pt_tag(pt_integer),
756                 new_size += sizeof(ref_packed);
757     } else {
758         obj_header_t *pfree = (obj_header_t *) ((ref *) dest + 1);
759 
760         pfree->o_alone = 0;
761         pfree->o_size = size - new_size - sizeof(obj_header_t);
762         pfree->o_type = &st_bytes;
763     }
764     /* Re-create the final ref. */
765     r_set_type((ref *) dest, t_integer);
766     dpre->o_size = new_size;
767 }
768