xref: /plan9/sys/src/cmd/gs/src/isave.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1993, 2000 Aladdin Enterprises.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /* $Id: isave.c,v 1.14 2005/06/23 07:35:30 igor Exp $ */
18 /* Save/restore manager for Ghostscript interpreter */
19 #include "ghost.h"
20 #include "memory_.h"
21 #include "ierrors.h"
22 #include "gsexit.h"
23 #include "gsstruct.h"
24 #include "stream.h"		/* for linking for forgetsave */
25 #include "iastate.h"
26 #include "inamedef.h"
27 #include "iname.h"
28 #include "ipacked.h"
29 #include "isave.h"
30 #include "isstate.h"
31 #include "store.h"		/* for ref_assign */
32 #include "ivmspace.h"
33 #include "gsutil.h"		/* gs_next_ids prototype */
34 
35 
36 /* Structure descriptor */
37 private_st_alloc_save();
38 
39 /* Define the maximum amount of data we are willing to scan repeatedly -- */
40 /* see below for details. */
41 private const long max_repeated_scan = 100000;
42 
43 /* Define the minimum space for creating an inner chunk. */
44 /* Must be at least sizeof(chunk_head_t). */
45 private const long min_inner_chunk_space = sizeof(chunk_head_t) + 500;
46 
47 /*
48  * The logic for saving and restoring the state is complex.
49  * Both the changes to individual objects, and the overall state
50  * of the memory manager, must be saved and restored.
51  */
52 
53 /*
54  * To save the state of the memory manager:
55  *      Save the state of the current chunk in which we are allocating.
56  *      Shrink all chunks to their inner unallocated region.
57  *      Save and reset the free block chains.
58  * By doing this, we guarantee that no object older than the save
59  * can be freed.
60  *
61  * To restore the state of the memory manager:
62  *      Free all chunks newer than the save, and the descriptors for
63  *        the inner chunks created by the save.
64  *      Make current the chunk that was current at the time of the save.
65  *      Restore the state of the current chunk.
66  *
67  * In addition to save ("start transaction") and restore ("abort transaction"),
68  * we support forgetting a save ("commit transation").  To forget a save:
69  *      Reassign to the next outer save all chunks newer than the save.
70  *      Free the descriptors for the inners chunk, updating their outer
71  *        chunks to reflect additional allocations in the inner chunks.
72  *      Concatenate the free block chains with those of the outer save.
73  */
74 
75 /*
76  * For saving changes to individual objects, we add an "attribute" bit
77  * (l_new) that logically belongs to the slot where the ref is stored,
78  * not to the ref itself.  The bit means "the contents of this slot
79  * have been changed, or the slot was allocated, since the last save."
80  * To keep track of changes since the save, we associate a chain of
81  * <slot, old_contents> pairs that remembers the old contents of slots.
82  *
83  * When creating an object, if the save level is non-zero:
84  *      Set l_new in all slots.
85  *
86  * When storing into a slot, if the save level is non-zero:
87  *      If l_new isn't set, save the address and contents of the slot
88  *        on the current contents chain.
89  *      Set l_new after storing the new value.
90  *
91  * To do a save:
92  *      If the save level is non-zero:
93  *              Reset l_new in all slots on the contents chain, and in all
94  *                objects created since the previous save.
95  *      Push the head of the contents chain, and reset the chain to empty.
96  *
97  * To do a restore:
98  *      Check all the stacks to make sure they don't contain references
99  *        to objects created since the save.
100  *      Restore all the slots on the contents chain.
101  *      Pop the contents chain head.
102  *      If the save level is now non-zero:
103  *              Scan the newly restored contents chain, and set l_new in all
104  *                the slots it references.
105  *              Scan all objects created since the previous save, and set
106  *                l_new in all the slots of each object.
107  *
108  * To forget a save:
109  *      If the save level is greater than 1:
110  *              Set l_new as for a restore, per the next outer save.
111  *              Concatenate the next outer contents chain to the end of
112  *                the current one.
113  *      If the save level is 1:
114  *              Reset l_new as for a save.
115  *              Free the contents chain.
116  */
117 
118 /*
119  * A consequence of the foregoing algorithms is that the cost of a save is
120  * proportional to the total amount of data allocated since the previous
121  * save.  If a PostScript program reads in a large amount of setup code and
122  * then uses save/restore heavily, each save/restore will be expensive.  To
123  * mitigate this, we check to see how much data we have scanned at this save
124  * level: if it is large, we do a second, invisible save.  This greatly
125  * reduces the cost of inner saves, at the expense of possibly saving some
126  * changes twice that otherwise would only have to be saved once.
127  */
128 
129 /*
130  * The presence of global and local VM complicates the situation further.
131  * There is a separate save chain and contents chain for each VM space.
132  * When multiple contexts are fully implemented, save and restore will have
133  * the following effects, according to the privacy status of the current
134  * context's global and local VM:
135  *      Private global, private local:
136  *              The outermost save saves both global and local VM;
137  *                otherwise, save only saves local VM.
138  *      Shared global, private local:
139  *              Save only saves local VM.
140  *      Shared global, shared local:
141  *              Save only saves local VM, and suspends all other contexts
142  *                sharing the same local VM until the matching restore.
143  * Since we do not currently implement multiple contexts, only the first
144  * case is relevant.
145  *
146  * Note that when saving the contents of a slot, the choice of chain
147  * is determined by the VM space in which the slot is allocated,
148  * not by the current allocation mode.
149  */
150 
151 /* Tracing printout */
152 private void
print_save(const char * str,uint spacen,const alloc_save_t * sav)153 print_save(const char *str, uint spacen, const alloc_save_t *sav)
154 {
155   if_debug5('u', "[u]%s space %u 0x%lx: cdata = 0x%lx, id = %lu\n",\
156 	    str, spacen, (ulong)sav, (ulong)sav->client_data, (ulong)sav->id);
157 }
158 
159 /*
160  * Structure for saved change chain for save/restore.  Because of the
161  * garbage collector, we need to distinguish the cases where the change
162  * is in a static object, a dynamic ref, or a dynamic struct.
163  */
164 typedef struct alloc_change_s alloc_change_t;
165 struct alloc_change_s {
166     alloc_change_t *next;
167     ref_packed *where;
168     ref contents;
169 #define AC_OFFSET_STATIC (-2)	/* static object */
170 #define AC_OFFSET_REF (-1)	/* dynamic ref */
171     short offset;		/* if >= 0, offset within struct */
172 };
173 
174 private
CLEAR_MARKS_PROC(change_clear_marks)175 CLEAR_MARKS_PROC(change_clear_marks)
176 {
177     alloc_change_t *const ptr = (alloc_change_t *)vptr;
178 
179     if (r_is_packed(&ptr->contents))
180 	r_clear_pmark((ref_packed *) & ptr->contents);
181     else
182 	r_clear_attrs(&ptr->contents, l_mark);
183 }
184 private
185 ENUM_PTRS_WITH(change_enum_ptrs, alloc_change_t *ptr) return 0;
186 ENUM_PTR(0, alloc_change_t, next);
187 case 1:
188     if (ptr->offset >= 0)
189 	ENUM_RETURN((byte *) ptr->where - ptr->offset);
190     else
191 	ENUM_RETURN_REF(ptr->where);
192 case 2:
193     ENUM_RETURN_REF(&ptr->contents);
194 ENUM_PTRS_END
RELOC_PTRS_WITH(change_reloc_ptrs,alloc_change_t * ptr)195 private RELOC_PTRS_WITH(change_reloc_ptrs, alloc_change_t *ptr)
196 {
197     RELOC_VAR(ptr->next);
198     switch (ptr->offset) {
199 	case AC_OFFSET_STATIC:
200 	    break;
201 	case AC_OFFSET_REF:
202 	    RELOC_REF_PTR_VAR(ptr->where);
203 	    break;
204 	default:
205 	    {
206 		byte *obj = (byte *) ptr->where - ptr->offset;
207 
208 		RELOC_VAR(obj);
209 		ptr->where = (ref_packed *) (obj + ptr->offset);
210 	    }
211 	    break;
212     }
213     if (r_is_packed(&ptr->contents))
214 	r_clear_pmark((ref_packed *) & ptr->contents);
215     else {
216 	RELOC_REF_VAR(ptr->contents);
217 	r_clear_attrs(&ptr->contents, l_mark);
218     }
219 }
220 RELOC_PTRS_END
221 gs_private_st_complex_only(st_alloc_change, alloc_change_t, "alloc_change",
222 		change_clear_marks, change_enum_ptrs, change_reloc_ptrs, 0);
223 
224 /* Debugging printout */
225 #ifdef DEBUG
226 private void
alloc_save_print(alloc_change_t * cp,bool print_current)227 alloc_save_print(alloc_change_t * cp, bool print_current)
228 {
229     dprintf2(" 0x%lx: 0x%lx: ", (ulong) cp, (ulong) cp->where);
230     if (r_is_packed(&cp->contents)) {
231 	if (print_current)
232 	    dprintf2("saved=%x cur=%x\n", *(ref_packed *) & cp->contents,
233 		     *cp->where);
234 	else
235 	    dprintf1("%x\n", *(ref_packed *) & cp->contents);
236     } else {
237 	if (print_current)
238 	    dprintf6("saved=%x %x %lx cur=%x %x %lx\n",
239 		     r_type_attrs(&cp->contents), r_size(&cp->contents),
240 		     (ulong) cp->contents.value.intval,
241 		     r_type_attrs((ref *) cp->where),
242 		     r_size((ref *) cp->where),
243 		     (ulong) ((ref *) cp->where)->value.intval);
244 	else
245 	    dprintf3("%x %x %lx\n",
246 		     r_type_attrs(&cp->contents), r_size(&cp->contents),
247 		     (ulong) cp->contents.value.intval);
248     }
249 }
250 #endif
251 
252 /* Forward references */
253 private void restore_resources(alloc_save_t *, gs_ref_memory_t *);
254 private void restore_free(gs_ref_memory_t *);
255 private long save_set_new(gs_ref_memory_t *, bool);
256 private void save_set_new_changes(gs_ref_memory_t *, bool);
257 
258 /* Initialize the save/restore machinery. */
259 void
alloc_save_init(gs_dual_memory_t * dmem)260 alloc_save_init(gs_dual_memory_t * dmem)
261 {
262     alloc_set_not_in_save(dmem);
263 }
264 
265 /* Record that we are in a save. */
266 private void
alloc_set_masks(gs_dual_memory_t * dmem,uint new_mask,uint test_mask)267 alloc_set_masks(gs_dual_memory_t *dmem, uint new_mask, uint test_mask)
268 {
269     int i;
270     gs_ref_memory_t *mem;
271 
272     dmem->new_mask = new_mask;
273     dmem->test_mask = test_mask;
274     for (i = 0; i < countof(dmem->spaces.memories.indexed); ++i)
275 	if ((mem = dmem->spaces.memories.indexed[i]) != 0) {
276 	    mem->new_mask = new_mask, mem->test_mask = test_mask;
277 	    if (mem->stable_memory != (gs_memory_t *)mem) {
278 		mem = (gs_ref_memory_t *)mem->stable_memory;
279 		mem->new_mask = new_mask, mem->test_mask = test_mask;
280 	    }
281 	}
282 }
283 void
alloc_set_in_save(gs_dual_memory_t * dmem)284 alloc_set_in_save(gs_dual_memory_t *dmem)
285 {
286     alloc_set_masks(dmem, l_new, l_new);
287 }
288 
289 /* Record that we are not in a save. */
290 void
alloc_set_not_in_save(gs_dual_memory_t * dmem)291 alloc_set_not_in_save(gs_dual_memory_t *dmem)
292 {
293     alloc_set_masks(dmem, 0, ~0);
294 }
295 
296 /* Save the state. */
297 private alloc_save_t *alloc_save_space(gs_ref_memory_t *mem,
298 				       gs_dual_memory_t *dmem,
299 				       ulong sid);
300 private void
alloc_free_save(gs_ref_memory_t * mem,alloc_save_t * save,const char * scn)301 alloc_free_save(gs_ref_memory_t *mem, alloc_save_t *save, const char *scn)
302 {
303     gs_free_object((gs_memory_t *)mem, save, scn);
304     /* Free any inner chunk structures.  This is the easiest way to do it. */
305     restore_free(mem);
306 }
307 ulong
alloc_save_state(gs_dual_memory_t * dmem,void * cdata)308 alloc_save_state(gs_dual_memory_t * dmem, void *cdata)
309 {
310     gs_ref_memory_t *lmem = dmem->space_local;
311     gs_ref_memory_t *gmem = dmem->space_global;
312     ulong sid = gs_next_ids((const gs_memory_t *)lmem->stable_memory, 2);
313     bool global =
314 	lmem->save_level == 0 && gmem != lmem &&
315 	gmem->num_contexts == 1;
316     alloc_save_t *gsave =
317 	(global ? alloc_save_space(gmem, dmem, sid + 1) : (alloc_save_t *) 0);
318     alloc_save_t *lsave = alloc_save_space(lmem, dmem, sid);
319 
320     if (lsave == 0 || (global && gsave == 0)) {
321 	if (lsave != 0)
322 	    alloc_free_save(lmem, lsave, "alloc_save_state(local save)");
323 	if (gsave != 0)
324 	    alloc_free_save(gmem, gsave, "alloc_save_state(global save)");
325 	return 0;
326     }
327     if (gsave != 0) {
328 	gsave->client_data = 0;
329 	print_save("save", gmem->space, gsave);
330 	/* Restore names when we do the local restore. */
331 	lsave->restore_names = gsave->restore_names;
332 	gsave->restore_names = false;
333     }
334     lsave->id = sid;
335     lsave->client_data = cdata;
336     print_save("save", lmem->space, lsave);
337     /* Reset the l_new attribute in all slots.  The only slots that */
338     /* can have the attribute set are the ones on the changes chain, */
339     /* and ones in objects allocated since the last save. */
340     if (lmem->save_level > 1) {
341 	long scanned = save_set_new(&lsave->state, false);
342 
343 	if ((lsave->state.total_scanned += scanned) > max_repeated_scan) {
344 	    /* Do a second, invisible save. */
345 	    alloc_save_t *rsave;
346 
347 	    rsave = alloc_save_space(lmem, dmem, 0L);
348 	    if (rsave != 0) {
349 		rsave->client_data = cdata;
350 #if 0 /* Bug 688153 */
351 		rsave->id = lsave->id;
352 		print_save("save", lmem->space, rsave);
353 		lsave->id = 0;	/* mark as invisible */
354 		rsave->state.save_level--; /* ditto */
355 		lsave->client_data = 0;
356 #else
357 		rsave->id = 0;  /* mark as invisible */
358 		print_save("save", lmem->space, rsave);
359 		rsave->state.save_level--; /* ditto */
360 		rsave->client_data = 0;
361 #endif
362 		/* Inherit the allocated space count -- */
363 		/* we need this for triggering a GC. */
364 		rsave->state.inherited =
365 		    lsave->state.allocated + lsave->state.inherited;
366 		lmem->inherited = rsave->state.inherited;
367 		print_save("save", lmem->space, lsave);
368 	    }
369 	}
370     }
371     alloc_set_in_save(dmem);
372     return sid;
373 }
374 /* Save the state of one space (global or local). */
375 private alloc_save_t *
alloc_save_space(gs_ref_memory_t * mem,gs_dual_memory_t * dmem,ulong sid)376 alloc_save_space(gs_ref_memory_t * mem, gs_dual_memory_t * dmem, ulong sid)
377 {
378     gs_ref_memory_t save_mem;
379     alloc_save_t *save;
380     chunk_t *cp;
381     chunk_t *new_pcc = 0;
382 
383     save_mem = *mem;
384     alloc_close_chunk(mem);
385     mem->pcc = 0;
386     gs_memory_status((gs_memory_t *) mem, &mem->previous_status);
387     ialloc_reset(mem);
388 
389     /* Create inner chunks wherever it's worthwhile. */
390 
391     for (cp = save_mem.cfirst; cp != 0; cp = cp->cnext) {
392 	if (cp->ctop - cp->cbot > min_inner_chunk_space) {
393 	    /* Create an inner chunk to cover only the unallocated part. */
394 	    chunk_t *inner =
395 		gs_raw_alloc_struct_immovable(mem->non_gc_memory, &st_chunk,
396 					      "alloc_save_space(inner)");
397 
398 	    if (inner == 0)
399 		break;		/* maybe should fail */
400 	    alloc_init_chunk(inner, cp->cbot, cp->ctop, cp->sreloc != 0, cp);
401 	    alloc_link_chunk(inner, mem);
402 	    if_debug2('u', "[u]inner chunk: cbot=0x%lx ctop=0x%lx\n",
403 		      (ulong) inner->cbot, (ulong) inner->ctop);
404 	    if (cp == save_mem.pcc)
405 		new_pcc = inner;
406 	}
407     }
408     mem->pcc = new_pcc;
409     alloc_open_chunk(mem);
410 
411     save = gs_alloc_struct((gs_memory_t *) mem, alloc_save_t,
412 			   &st_alloc_save, "alloc_save_space(save)");
413     if_debug2('u', "[u]save space %u at 0x%lx\n",
414 	      mem->space, (ulong) save);
415     if (save == 0) {
416 	/* Free the inner chunk structures.  This is the easiest way. */
417 	restore_free(mem);
418 	*mem = save_mem;
419 	return 0;
420     }
421     save->state = save_mem;
422     save->spaces = dmem->spaces;
423     save->restore_names = (name_memory(mem) == (gs_memory_t *) mem);
424     save->is_current = (dmem->current == mem);
425     save->id = sid;
426     mem->saved = save;
427     if_debug2('u', "[u%u]file_save 0x%lx\n",
428 	      mem->space, (ulong) mem->streams);
429     mem->streams = 0;
430     mem->total_scanned = 0;
431     if (sid)
432 	mem->save_level++;
433     return save;
434 }
435 
436 /* Record a state change that must be undone for restore, */
437 /* and mark it as having been saved. */
438 int
alloc_save_change_in(gs_ref_memory_t * mem,const ref * pcont,ref_packed * where,client_name_t cname)439 alloc_save_change_in(gs_ref_memory_t *mem, const ref * pcont,
440 		  ref_packed * where, client_name_t cname)
441 {
442     register alloc_change_t *cp;
443 
444     if (mem->new_mask == 0)
445 	return 0;		/* no saving */
446     cp = gs_alloc_struct((gs_memory_t *)mem, alloc_change_t,
447 			 &st_alloc_change, "alloc_save_change");
448     if (cp == 0)
449 	return -1;
450     cp->next = mem->changes;
451     cp->where = where;
452     if (pcont == NULL)
453 	cp->offset = AC_OFFSET_STATIC;
454     else if (r_is_array(pcont) || r_has_type(pcont, t_dictionary))
455 	cp->offset = AC_OFFSET_REF;
456     else if (r_is_struct(pcont))
457 	cp->offset = (byte *) where - (byte *) pcont->value.pstruct;
458     else {
459 	lprintf3("Bad type %u for save!  pcont = 0x%lx, where = 0x%lx\n",
460 		 r_type(pcont), (ulong) pcont, (ulong) where);
461 	gs_abort((const gs_memory_t *)mem);
462     }
463     if (r_is_packed(where))
464 	*(ref_packed *)&cp->contents = *where;
465     else {
466 	ref_assign_inline(&cp->contents, (ref *) where);
467 	r_set_attrs((ref *) where, l_new);
468     }
469     mem->changes = cp;
470 #ifdef DEBUG
471     if (gs_debug_c('U')) {
472 	dlprintf1("[U]save(%s)", client_name_string(cname));
473 	alloc_save_print(cp, false);
474     }
475 #endif
476     return 0;
477 }
478 int
alloc_save_change(gs_dual_memory_t * dmem,const ref * pcont,ref_packed * where,client_name_t cname)479 alloc_save_change(gs_dual_memory_t * dmem, const ref * pcont,
480 		  ref_packed * where, client_name_t cname)
481 {
482     gs_ref_memory_t *mem =
483 	(pcont == NULL ? dmem->space_local :
484 	 dmem->spaces_indexed[r_space(pcont) >> r_space_shift]);
485 
486     return alloc_save_change_in(mem, pcont, where, cname);
487 }
488 
489 /* Return (the id of) the innermost externally visible save object, */
490 /* i.e., the innermost save with a non-zero ID. */
491 ulong
alloc_save_current_id(const gs_dual_memory_t * dmem)492 alloc_save_current_id(const gs_dual_memory_t * dmem)
493 {
494     const alloc_save_t *save = dmem->space_local->saved;
495 
496     while (save != 0 && save->id == 0)
497 	save = save->state.saved;
498     return save->id;
499 }
500 alloc_save_t *
alloc_save_current(const gs_dual_memory_t * dmem)501 alloc_save_current(const gs_dual_memory_t * dmem)
502 {
503     return alloc_find_save(dmem, alloc_save_current_id(dmem));
504 }
505 
506 /* Test whether a reference would be invalidated by a restore. */
507 bool
alloc_is_since_save(const void * vptr,const alloc_save_t * save)508 alloc_is_since_save(const void *vptr, const alloc_save_t * save)
509 {
510     /* A reference postdates a save iff it is in a chunk allocated */
511     /* since the save (including any carried-over inner chunks). */
512 
513     const char *const ptr = (const char *)vptr;
514     register const gs_ref_memory_t *mem = save->space_local;
515 
516     if_debug2('U', "[U]is_since_save 0x%lx, 0x%lx:\n",
517 	      (ulong) ptr, (ulong) save);
518     if (mem->saved == 0) {	/* This is a special case, the final 'restore' from */
519 	/* alloc_restore_all. */
520 	return true;
521     }
522     /* Check against chunks allocated since the save. */
523     /* (There may have been intermediate saves as well.) */
524     for (;; mem = &mem->saved->state) {
525 	const chunk_t *cp;
526 
527 	if_debug1('U', "[U]checking mem=0x%lx\n", (ulong) mem);
528 	for (cp = mem->cfirst; cp != 0; cp = cp->cnext) {
529 	    if (ptr_is_within_chunk(ptr, cp)) {
530 		if_debug3('U', "[U+]in new chunk 0x%lx: 0x%lx, 0x%lx\n",
531 			  (ulong) cp,
532 			  (ulong) cp->cbase, (ulong) cp->cend);
533 		return true;
534 	    }
535 	    if_debug1('U', "[U-]not in 0x%lx\n", (ulong) cp);
536 	}
537 	if (mem->saved == save) {	/* We've checked all the more recent saves, */
538 	    /* must be OK. */
539 	    break;
540 	}
541     }
542 
543     /*
544      * If we're about to do a global restore (a restore to the level 0),
545      * and there is only one context using this global VM
546      * (the normal case, in which global VM is saved by the
547      * outermost save), we also have to check the global save.
548      * Global saves can't be nested, which makes things easy.
549      */
550     if (save->state.save_level == 0 /* Restoring to save level 0 - see bug 688157, 688161 */ &&
551 	(mem = save->space_global) != save->space_local &&
552 	save->space_global->num_contexts == 1
553 	) {
554 	const chunk_t *cp;
555 
556 	if_debug1('U', "[U]checking global mem=0x%lx\n", (ulong) mem);
557 	for (cp = mem->cfirst; cp != 0; cp = cp->cnext)
558 	    if (ptr_is_within_chunk(ptr, cp)) {
559 		if_debug3('U', "[U+]  new chunk 0x%lx: 0x%lx, 0x%lx\n",
560 			  (ulong) cp, (ulong) cp->cbase, (ulong) cp->cend);
561 		return true;
562 	    }
563     }
564     return false;
565 
566 #undef ptr
567 }
568 
569 /* Test whether a name would be invalidated by a restore. */
570 bool
alloc_name_is_since_save(const gs_memory_t * mem,const ref * pnref,const alloc_save_t * save)571 alloc_name_is_since_save(const gs_memory_t *mem,
572 			 const ref * pnref, const alloc_save_t * save)
573 {
574     const name_string_t *pnstr;
575 
576     if (!save->restore_names)
577 	return false;
578     pnstr = names_string_inline(mem->gs_lib_ctx->gs_name_table, pnref);
579     if (pnstr->foreign_string)
580 	return false;
581     return alloc_is_since_save(pnstr->string_bytes, save);
582 }
583 bool
alloc_name_index_is_since_save(const gs_memory_t * mem,uint nidx,const alloc_save_t * save)584 alloc_name_index_is_since_save(const gs_memory_t *mem,
585 			       uint nidx, const alloc_save_t *save)
586 {
587     const name_string_t *pnstr;
588 
589     if (!save->restore_names)
590 	return false;
591     pnstr = names_index_string_inline(mem->gs_lib_ctx->gs_name_table, nidx);
592     if (pnstr->foreign_string)
593 	return false;
594     return alloc_is_since_save(pnstr->string_bytes, save);
595 }
596 
597 /* Check whether any names have been created since a given save */
598 /* that might be released by the restore. */
599 bool
alloc_any_names_since_save(const alloc_save_t * save)600 alloc_any_names_since_save(const alloc_save_t * save)
601 {
602     return save->restore_names;
603 }
604 
605 /* Get the saved state with a given ID. */
606 alloc_save_t *
alloc_find_save(const gs_dual_memory_t * dmem,ulong sid)607 alloc_find_save(const gs_dual_memory_t * dmem, ulong sid)
608 {
609     alloc_save_t *sprev = dmem->space_local->saved;
610 
611     if (sid == 0)
612 	return 0;		/* invalid id */
613     while (sprev != 0) {
614 	if (sprev->id == sid)
615 	    return sprev;
616 	sprev = sprev->state.saved;
617     }
618     return 0;
619 }
620 
621 /* Get the client data from a saved state. */
622 void *
alloc_save_client_data(const alloc_save_t * save)623 alloc_save_client_data(const alloc_save_t * save)
624 {
625     return save->client_data;
626 }
627 
628 /*
629  * Do one step of restoring the state.  The client is responsible for
630  * calling alloc_find_save to get the save object, and for ensuring that
631  * there are no surviving pointers for which alloc_is_since_save is true.
632  * Return true if the argument was the innermost save, in which case
633  * this is the last (or only) step.
634  * Note that "one step" may involve multiple internal steps,
635  * if this is the outermost restore (which requires restoring both local
636  * and global VM) or if we created extra save levels to reduce scanning.
637  */
638 private void restore_finalize(gs_ref_memory_t *);
639 private void restore_space(gs_ref_memory_t *, gs_dual_memory_t *);
640 
641 bool
alloc_restore_step_in(gs_dual_memory_t * dmem,alloc_save_t * save)642 alloc_restore_step_in(gs_dual_memory_t *dmem, alloc_save_t * save)
643 {
644     /* Get save->space_* now, because the save object will be freed. */
645     gs_ref_memory_t *lmem = save->space_local;
646     gs_ref_memory_t *gmem = save->space_global;
647     gs_ref_memory_t *mem = lmem;
648     alloc_save_t *sprev;
649 
650     /* Finalize all objects before releasing resources or undoing changes. */
651     do {
652 	ulong sid;
653 
654 	sprev = mem->saved;
655 	sid = sprev->id;
656 	restore_finalize(mem);	/* finalize objects */
657 	mem = &sprev->state;
658 	if (sid != 0)
659 	    break;
660     }
661     while (sprev != save);
662     if (mem->save_level == 0) {
663 	/* This is the outermost save, which might also */
664 	/* need to restore global VM. */
665 	mem = gmem;
666 	if (mem != lmem && mem->saved != 0)
667 	    restore_finalize(mem);
668     }
669 
670     /* Do one (externally visible) step of restoring the state. */
671     mem = lmem;
672     do {
673 	ulong sid;
674 
675 	sprev = mem->saved;
676 	sid = sprev->id;
677 	restore_resources(sprev, mem);	/* release other resources */
678 	restore_space(mem, dmem);	/* release memory */
679 	if (sid != 0)
680 	    break;
681     }
682     while (sprev != save);
683 
684     if (mem->save_level == 0) {
685 	/* This is the outermost save, which might also */
686 	/* need to restore global VM. */
687 	mem = gmem;
688 	if (mem != lmem && mem->saved != 0) {
689 	    restore_resources(mem->saved, mem);
690 	    restore_space(mem, dmem);
691 	}
692 	alloc_set_not_in_save(dmem);
693     } else {			/* Set the l_new attribute in all slots that are now new. */
694 	save_set_new(mem, true);
695     }
696 
697     return sprev == save;
698 }
699 /* Restore the memory of one space, by undoing changes and freeing */
700 /* memory allocated since the save. */
701 private void
restore_space(gs_ref_memory_t * mem,gs_dual_memory_t * dmem)702 restore_space(gs_ref_memory_t * mem, gs_dual_memory_t *dmem)
703 {
704     alloc_save_t *save = mem->saved;
705     alloc_save_t saved;
706 
707     print_save("restore", mem->space, save);
708 
709     /* Undo changes since the save. */
710     {
711 	register alloc_change_t *cp = mem->changes;
712 
713 	while (cp) {
714 #ifdef DEBUG
715 	    if (gs_debug_c('U')) {
716 		dlputs("[U]restore");
717 		alloc_save_print(cp, true);
718 	    }
719 #endif
720 	    if (r_is_packed(&cp->contents))
721 		*cp->where = *(ref_packed *) & cp->contents;
722 	    else
723 		ref_assign_inline((ref *) cp->where, &cp->contents);
724 	    cp = cp->next;
725 	}
726     }
727 
728     /* Free memory allocated since the save. */
729     /* Note that this frees all chunks except the inner ones */
730     /* belonging to this level. */
731     saved = *save;
732     restore_free(mem);
733 
734     /* Restore the allocator state. */
735     {
736 	int num_contexts = mem->num_contexts;	/* don't restore */
737 
738 	*mem = saved.state;
739 	mem->num_contexts = num_contexts;
740     }
741     alloc_open_chunk(mem);
742 
743     /* Make the allocator current if it was current before the save. */
744     if (saved.is_current) {
745 	dmem->current = mem;
746 	dmem->current_space = mem->space;
747     }
748 }
749 
750 /* Restore to the initial state, releasing all resources. */
751 /* The allocator is no longer usable after calling this routine! */
752 void
alloc_restore_all(gs_dual_memory_t * dmem)753 alloc_restore_all(gs_dual_memory_t * dmem)
754 {
755     /*
756      * Save the memory pointers, since freeing space_local will also
757      * free dmem itself.
758      */
759     gs_ref_memory_t *lmem = dmem->space_local;
760     gs_ref_memory_t *gmem = dmem->space_global;
761     gs_ref_memory_t *smem = dmem->space_system;
762     gs_ref_memory_t *mem;
763 
764     /* Restore to a state outside any saves. */
765     while (lmem->save_level != 0)
766 	discard(alloc_restore_step_in(dmem, lmem->saved));
767 
768     /* Finalize memory. */
769     restore_finalize(lmem);
770     if ((mem = (gs_ref_memory_t *)lmem->stable_memory) != lmem)
771 	restore_finalize(mem);
772     if (gmem != lmem && gmem->num_contexts == 1) {
773 	restore_finalize(gmem);
774 	if ((mem = (gs_ref_memory_t *)gmem->stable_memory) != gmem)
775 	    restore_finalize(mem);
776     }
777     restore_finalize(smem);
778 
779     /* Release resources other than memory, using fake */
780     /* save and memory objects. */
781     {
782 	alloc_save_t empty_save;
783 
784 	empty_save.spaces = dmem->spaces;
785 	empty_save.restore_names = false;	/* don't bother to release */
786 	restore_resources(&empty_save, NULL);
787     }
788 
789     /* Finally, release memory. */
790     restore_free(lmem);
791     if ((mem = (gs_ref_memory_t *)lmem->stable_memory) != lmem)
792 	restore_free(mem);
793     if (gmem != lmem) {
794 	if (!--(gmem->num_contexts)) {
795 	    restore_free(gmem);
796 	    if ((mem = (gs_ref_memory_t *)gmem->stable_memory) != gmem)
797 		restore_free(mem);
798 	}
799     }
800     restore_free(smem);
801 
802 }
803 
804 /*
805  * Finalize objects that will be freed by a restore.
806  * Note that we must temporarily disable the freeing operations
807  * of the allocator while doing this.
808  */
809 private void
restore_finalize(gs_ref_memory_t * mem)810 restore_finalize(gs_ref_memory_t * mem)
811 {
812     chunk_t *cp;
813 
814     alloc_close_chunk(mem);
815     gs_enable_free((gs_memory_t *) mem, false);
816     for (cp = mem->clast; cp != 0; cp = cp->cprev) {
817 	SCAN_CHUNK_OBJECTS(cp)
818 	    DO_ALL
819 	    struct_proc_finalize((*finalize)) =
820 	    pre->o_type->finalize;
821 	if (finalize != 0) {
822 	    if_debug2('u', "[u]restore finalizing %s 0x%lx\n",
823 		      struct_type_name_string(pre->o_type),
824 		      (ulong) (pre + 1));
825 	    (*finalize) (pre + 1);
826 	}
827 	END_OBJECTS_SCAN
828     }
829     gs_enable_free((gs_memory_t *) mem, true);
830 }
831 
832 /* Release resources for a restore */
833 private void
restore_resources(alloc_save_t * sprev,gs_ref_memory_t * mem)834 restore_resources(alloc_save_t * sprev, gs_ref_memory_t * mem)
835 {
836 #ifdef DEBUG
837     if (mem) {
838 	/* Note restoring of the file list. */
839 	if_debug4('u', "[u%u]file_restore 0x%lx => 0x%lx for 0x%lx\n",
840 		  mem->space, (ulong)mem->streams,
841 		  (ulong)sprev->state.streams, (ulong) sprev);
842     }
843 #endif
844 
845     /* Remove entries from font and character caches. */
846     font_restore(sprev);
847 
848     /* Adjust the name table. */
849     if (sprev->restore_names)
850 	names_restore(mem->gs_lib_ctx->gs_name_table, sprev);
851 }
852 
853 /* Release memory for a restore. */
854 private void
restore_free(gs_ref_memory_t * mem)855 restore_free(gs_ref_memory_t * mem)
856 {
857     /* Free chunks allocated since the save. */
858     gs_free_all((gs_memory_t *) mem);
859 }
860 
861 /* Forget a save, by merging this level with the next outer one. */
862 private void file_forget_save(gs_ref_memory_t *);
863 private void combine_space(gs_ref_memory_t *);
864 private void forget_changes(gs_ref_memory_t *);
865 void
alloc_forget_save_in(gs_dual_memory_t * dmem,alloc_save_t * save)866 alloc_forget_save_in(gs_dual_memory_t *dmem, alloc_save_t * save)
867 {
868     gs_ref_memory_t *mem = save->space_local;
869     alloc_save_t *sprev;
870 
871     print_save("forget_save", mem->space, save);
872 
873     /* Iteratively combine the current level with the previous one. */
874     do {
875 	sprev = mem->saved;
876 	if (sprev->id != 0)
877 	    mem->save_level--;
878 	if (mem->save_level != 0) {
879 	    alloc_change_t *chp = mem->changes;
880 
881 	    save_set_new(&sprev->state, true);
882 	    /* Concatenate the changes chains. */
883 	    if (chp == 0)
884 		mem->changes = sprev->state.changes;
885 	    else {
886 		while (chp->next != 0)
887 		    chp = chp->next;
888 		chp->next = sprev->state.changes;
889 	    }
890 	    file_forget_save(mem);
891 	    combine_space(mem);	/* combine memory */
892 	} else {
893 	    forget_changes(mem);
894 	    save_set_new(mem, false);
895 	    file_forget_save(mem);
896 	    combine_space(mem);	/* combine memory */
897 	    /* This is the outermost save, which might also */
898 	    /* need to combine global VM. */
899 	    mem = save->space_global;
900 	    if (mem != save->space_local && mem->saved != 0) {
901 		forget_changes(mem);
902 		save_set_new(mem, false);
903 		file_forget_save(mem);
904 		combine_space(mem);
905 	    }
906 	    alloc_set_not_in_save(dmem);
907 	    break;		/* must be outermost */
908 	}
909     }
910     while (sprev != save);
911 }
912 /* Combine the chunks of the next outer level with those of the current one, */
913 /* and free the bookkeeping structures. */
914 private void
combine_space(gs_ref_memory_t * mem)915 combine_space(gs_ref_memory_t * mem)
916 {
917     alloc_save_t *saved = mem->saved;
918     gs_ref_memory_t *omem = &saved->state;
919     chunk_t *cp;
920     chunk_t *csucc;
921 
922     alloc_close_chunk(mem);
923     for (cp = mem->cfirst; cp != 0; cp = csucc) {
924 	csucc = cp->cnext;	/* save before relinking */
925 	if (cp->outer == 0)
926 	    alloc_link_chunk(cp, omem);
927 	else {
928 	    chunk_t *outer = cp->outer;
929 
930 	    outer->inner_count--;
931 	    if (mem->pcc == cp)
932 		mem->pcc = outer;
933 	    if (mem->cfreed.cp == cp)
934 		mem->cfreed.cp = outer;
935 	    /* "Free" the header of the inner chunk, */
936 	    /* and any immediately preceding gap left by */
937 	    /* the GC having compacted the outer chunk. */
938 	    {
939 		obj_header_t *hp = (obj_header_t *) outer->cbot;
940 
941 		hp->o_alone = 0;
942 		hp->o_size = (char *)(cp->chead + 1)
943 		    - (char *)(hp + 1);
944 		hp->o_type = &st_bytes;
945 		/* The following call is probably not safe. */
946 #if 0				/* **************** */
947 		gs_free_object((gs_memory_t *) mem,
948 			       hp + 1, "combine_space(header)");
949 #endif /* **************** */
950 	    }
951 	    /* Update the outer chunk's allocation pointers. */
952 	    outer->cbot = cp->cbot;
953 	    outer->rcur = cp->rcur;
954 	    outer->rtop = cp->rtop;
955 	    outer->ctop = cp->ctop;
956 	    outer->has_refs |= cp->has_refs;
957 	    gs_free_object(mem->non_gc_memory, cp,
958 			   "combine_space(inner)");
959 	}
960     }
961     /* Update relevant parts of allocator state. */
962     mem->cfirst = omem->cfirst;
963     mem->clast = omem->clast;
964     mem->allocated += omem->allocated;
965     mem->gc_allocated += omem->allocated;
966     mem->lost.objects += omem->lost.objects;
967     mem->lost.refs += omem->lost.refs;
968     mem->lost.strings += omem->lost.strings;
969     mem->saved = omem->saved;
970     mem->previous_status = omem->previous_status;
971     {				/* Concatenate free lists. */
972 	int i;
973 
974 	for (i = 0; i < num_freelists; i++) {
975 	    obj_header_t *olist = omem->freelists[i];
976 	    obj_header_t *list = mem->freelists[i];
977 
978 	    if (olist == 0);
979 	    else if (list == 0)
980 		mem->freelists[i] = olist;
981 	    else {
982 		while (*(obj_header_t **) list != 0)
983 		    list = *(obj_header_t **) list;
984 		*(obj_header_t **) list = olist;
985 	    }
986 	}
987 	if (omem->largest_free_size > mem->largest_free_size)
988 	    mem->largest_free_size = omem->largest_free_size;
989     }
990     gs_free_object((gs_memory_t *) mem, saved, "combine_space(saved)");
991     alloc_open_chunk(mem);
992 }
993 /* Free the changes chain for a level 0 .forgetsave, */
994 /* resetting the l_new flag in the changed refs. */
995 private void
forget_changes(gs_ref_memory_t * mem)996 forget_changes(gs_ref_memory_t * mem)
997 {
998     register alloc_change_t *chp = mem->changes;
999     alloc_change_t *next;
1000 
1001     for (; chp; chp = next) {
1002 	ref_packed *prp = chp->where;
1003 
1004 	if_debug1('U', "[U]forgetting change 0x%lx\n", (ulong) chp);
1005 	if (!r_is_packed(prp))
1006 	    r_clear_attrs((ref *) prp, l_new);
1007 	next = chp->next;
1008 	gs_free_object((gs_memory_t *) mem, chp, "forget_changes");
1009     }
1010     mem->changes = 0;
1011 }
1012 /* Update the streams list when forgetting a save. */
1013 private void
file_forget_save(gs_ref_memory_t * mem)1014 file_forget_save(gs_ref_memory_t * mem)
1015 {
1016     const alloc_save_t *save = mem->saved;
1017     stream *streams = mem->streams;
1018     stream *saved_streams = save->state.streams;
1019 
1020     if_debug4('u', "[u%d]file_forget_save 0x%lx + 0x%lx for 0x%lx\n",
1021 	      mem->space, (ulong) streams, (ulong) saved_streams,
1022 	      (ulong) save);
1023     if (streams == 0)
1024 	mem->streams = saved_streams;
1025     else if (saved_streams != 0) {
1026 	while (streams->next != 0)
1027 	    streams = streams->next;
1028 	streams->next = saved_streams;
1029 	saved_streams->prev = streams;
1030     }
1031 }
1032 
1033 /* ------ Internal routines ------ */
1034 
1035 /* Set or reset the l_new attribute in every relevant slot. */
1036 /* This includes every slot on the current change chain, */
1037 /* and every (ref) slot allocated at this save level. */
1038 /* Return the number of bytes of data scanned. */
1039 private long
save_set_new(gs_ref_memory_t * mem,bool to_new)1040 save_set_new(gs_ref_memory_t * mem, bool to_new)
1041 {
1042     long scanned = 0;
1043 
1044     /* Handle the change chain. */
1045     save_set_new_changes(mem, to_new);
1046 
1047     /* Handle newly allocated ref objects. */
1048     SCAN_MEM_CHUNKS(mem, cp) {
1049 	if (cp->has_refs) {
1050 	    bool has_refs = false;
1051 
1052 	    SCAN_CHUNK_OBJECTS(cp)
1053 		DO_ALL
1054 		if_debug3('U', "[U]set_new scan(0x%lx(%u), %d)\n",
1055 			  (ulong) pre, size, to_new);
1056 	    if (pre->o_type == &st_refs) {
1057 		/* These are refs, scan them. */
1058 		ref_packed *prp = (ref_packed *) (pre + 1);
1059 		ref_packed *next = (ref_packed *) ((char *)prp + size);
1060 #ifdef ALIGNMENT_ALIASING_BUG
1061 		ref *rpref;
1062 # define RP_REF(rp) (rpref = (ref *)rp, rpref)
1063 #else
1064 # define RP_REF(rp) ((ref *)rp)
1065 #endif
1066 
1067 		if_debug2('U', "[U]refs 0x%lx to 0x%lx\n",
1068 			  (ulong) prp, (ulong) next);
1069 		has_refs = true;
1070 		scanned += size;
1071 		/* We know that every block of refs ends with */
1072 		/* a full-size ref, so we only need the end check */
1073 		/* when we encounter one of those. */
1074 		if (to_new)
1075 		    while (1) {
1076 			if (r_is_packed(prp))
1077 			    prp++;
1078 			else {
1079 			    RP_REF(prp)->tas.type_attrs |= l_new;
1080 			    prp += packed_per_ref;
1081 			    if (prp >= next)
1082 				break;
1083 			}
1084 		} else
1085 		    while (1) {
1086 			if (r_is_packed(prp))
1087 			    prp++;
1088 			else {
1089 			    RP_REF(prp)->tas.type_attrs &= ~l_new;
1090 			    prp += packed_per_ref;
1091 			    if (prp >= next)
1092 				break;
1093 			}
1094 		    }
1095 #undef RP_REF
1096 	    } else
1097 		scanned += sizeof(obj_header_t);
1098 	    END_OBJECTS_SCAN
1099 		cp->has_refs = has_refs;
1100 	}
1101     }
1102     END_CHUNKS_SCAN
1103 	if_debug2('u', "[u]set_new (%s) scanned %ld\n",
1104 		  (to_new ? "restore" : "save"), scanned);
1105     return scanned;
1106 }
1107 
1108 /* Set or reset the l_new attribute on the changes chain. */
1109 private void
save_set_new_changes(gs_ref_memory_t * mem,bool to_new)1110 save_set_new_changes(gs_ref_memory_t * mem, bool to_new)
1111 {
1112     register alloc_change_t *chp = mem->changes;
1113     register uint new = (to_new ? l_new : 0);
1114 
1115     for (; chp; chp = chp->next) {
1116 	ref_packed *prp = chp->where;
1117 
1118 	if_debug3('U', "[U]set_new 0x%lx: (0x%lx, %d)\n",
1119 		  (ulong)chp, (ulong)prp, new);
1120 	if (!r_is_packed(prp)) {
1121 	    ref *const rp = (ref *) prp;
1122 
1123 	    rp->tas.type_attrs =
1124 		(rp->tas.type_attrs & ~l_new) + new;
1125 	}
1126     }
1127 }
1128