/* Copyright (C) 1993, 2000 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: isave.c,v 1.3.6.1.2.1 2003/01/17 00:49:04 giles Exp $ */ /* Save/restore manager for Ghostscript interpreter */ #include "ghost.h" #include "memory_.h" #include "errors.h" #include "gsexit.h" #include "gsstruct.h" #include "stream.h" /* for linking for forgetsave */ #include "iastate.h" #include "inamedef.h" #include "iname.h" #include "ipacked.h" #include "isave.h" #include "isstate.h" #include "store.h" /* for ref_assign */ #include "ivmspace.h" #include "gsutil.h" /* gs_next_ids prototype */ /* Imported save/restore routines */ extern void font_restore(P1(const alloc_save_t *)); /* Structure descriptor */ private_st_alloc_save(); /* Define the maximum amount of data we are willing to scan repeatedly -- */ /* see below for details. */ private const long max_repeated_scan = 100000; /* * The logic for saving and restoring the state is complex. * Both the changes to individual objects, and the overall state * of the memory manager, must be saved and restored. */ /* * To save the state of the memory manager: * Save the state of the current chunk in which we are allocating. * Shrink the current chunk to its inner unallocated region. * Save and reset the free block chains. * By doing this, we guarantee that no object older than the save * can be freed. * * To restore the state of the memory manager: * Free all chunks newer than the save, and the descriptor for * the inner chunk created by the save. * Make current the chunk that was current at the time of the save. * Restore the state of the current chunk. * * In addition to save ("start transaction") and restore ("abort transaction"), * we support forgetting a save ("commit transation"). To forget a save: * Reassign to the next outer save all chunks newer than the save. * Free the descriptor for the inner chunk, updating its outer chunk * to reflect additional allocations in the inner chunk. * Concatenate the free block chains with those of the outer save. */ /* * For saving changes to individual objects, we add an "attribute" bit * (l_new) that logically belongs to the slot where the ref is stored, * not to the ref itself. The bit means "the contents of this slot * have been changed, or the slot was allocated, since the last save." * To keep track of changes since the save, we associate a chain of * pairs that remembers the old contents of slots. * * When creating an object, if the save level is non-zero: * Set l_new in all slots. * * When storing into a slot, if the save level is non-zero: * If l_new isn't set, save the address and contents of the slot * on the current contents chain. * Set l_new after storing the new value. * * To do a save: * If the save level is non-zero: * Reset l_new in all slots on the contents chain, and in all * objects created since the previous save. * Push the head of the contents chain, and reset the chain to empty. * * To do a restore: * Check all the stacks to make sure they don't contain references * to objects created since the save. * Restore all the slots on the contents chain. * Pop the contents chain head. * If the save level is now non-zero: * Scan the newly restored contents chain, and set l_new in all * the slots it references. * Scan all objects created since the previous save, and set * l_new in all the slots of each object. * * To forget a save: * If the save level is greater than 1: * Set l_new as for a restore, per the next outer save. * Concatenate the next outer contents chain to the end of * the current one. * If the save level is 1: * Reset l_new as for a save. * Free the contents chain. */ /* * A consequence of the foregoing algorithms is that the cost of a save is * proportional to the total amount of data allocated since the previous * save. If a PostScript program reads in a large amount of setup code and * then uses save/restore heavily, each save/restore will be expensive. To * mitigate this, we check to see how much data we have scanned at this save * level: if it is large, we do a second, invisible save. This greatly * reduces the cost of inner saves, at the expense of possibly saving some * changes twice that otherwise would only have to be saved once. */ /* * The presence of global and local VM complicates the situation further. * There is a separate save chain and contents chain for each VM space. * When multiple contexts are fully implemented, save and restore will have * the following effects, according to the privacy status of the current * context's global and local VM: * Private global, private local: * The outermost save saves both global and local VM; * otherwise, save only saves local VM. * Shared global, private local: * Save only saves local VM. * Shared global, shared local: * Save only saves local VM, and suspends all other contexts * sharing the same local VM until the matching restore. * Since we do not currently implement multiple contexts, only the first * case is relevant. * * Note that when saving the contents of a slot, the choice of chain * is determined by the VM space in which the slot is allocated, * not by the current allocation mode. */ /* Tracing printout */ private void print_save(const char *str, uint spacen, const alloc_save_t *sav) { if_debug5('u', "[u]%s space %u 0x%lx: cdata = 0x%lx, id = %lu\n",\ str, spacen, (ulong)sav, (ulong)sav->client_data, (ulong)sav->id); } /* * Structure for saved change chain for save/restore. Because of the * garbage collector, we need to distinguish the cases where the change * is in a static object, a dynamic ref, or a dynamic struct. */ typedef struct alloc_change_s alloc_change_t; struct alloc_change_s { alloc_change_t *next; ref_packed *where; ref contents; #define AC_OFFSET_STATIC (-2) /* static object */ #define AC_OFFSET_REF (-1) /* dynamic ref */ short offset; /* if >= 0, offset within struct */ }; private CLEAR_MARKS_PROC(change_clear_marks) { alloc_change_t *const ptr = (alloc_change_t *)vptr; if (r_is_packed(&ptr->contents)) r_clear_pmark((ref_packed *) & ptr->contents); else r_clear_attrs(&ptr->contents, l_mark); } private ENUM_PTRS_WITH(change_enum_ptrs, alloc_change_t *ptr) return 0; ENUM_PTR(0, alloc_change_t, next); case 1: if (ptr->offset >= 0) ENUM_RETURN((byte *) ptr->where - ptr->offset); else ENUM_RETURN_REF(ptr->where); case 2: ENUM_RETURN_REF(&ptr->contents); ENUM_PTRS_END private RELOC_PTRS_WITH(change_reloc_ptrs, alloc_change_t *ptr) { RELOC_VAR(ptr->next); switch (ptr->offset) { case AC_OFFSET_STATIC: break; case AC_OFFSET_REF: RELOC_REF_PTR_VAR(ptr->where); break; default: { byte *obj = (byte *) ptr->where - ptr->offset; RELOC_VAR(obj); ptr->where = (ref_packed *) (obj + ptr->offset); } break; } if (r_is_packed(&ptr->contents)) r_clear_pmark((ref_packed *) & ptr->contents); else { RELOC_REF_VAR(ptr->contents); r_clear_attrs(&ptr->contents, l_mark); } } RELOC_PTRS_END gs_private_st_complex_only(st_alloc_change, alloc_change_t, "alloc_change", change_clear_marks, change_enum_ptrs, change_reloc_ptrs, 0); /* Debugging printout */ #ifdef DEBUG private void alloc_save_print(alloc_change_t * cp, bool print_current) { dprintf2(" 0x%lx: 0x%lx: ", (ulong) cp, (ulong) cp->where); if (r_is_packed(&cp->contents)) { if (print_current) dprintf2("saved=%x cur=%x\n", *(ref_packed *) & cp->contents, *cp->where); else dprintf1("%x\n", *(ref_packed *) & cp->contents); } else { if (print_current) dprintf6("saved=%x %x %lx cur=%x %x %lx\n", r_type_attrs(&cp->contents), r_size(&cp->contents), (ulong) cp->contents.value.intval, r_type_attrs((ref *) cp->where), r_size((ref *) cp->where), (ulong) ((ref *) cp->where)->value.intval); else dprintf3("%x %x %lx\n", r_type_attrs(&cp->contents), r_size(&cp->contents), (ulong) cp->contents.value.intval); } } #endif /* Forward references */ private void restore_resources(P2(alloc_save_t *, gs_ref_memory_t *)); private void restore_free(P1(gs_ref_memory_t *)); private long save_set_new(P2(gs_ref_memory_t *, bool)); private void save_set_new_changes(P2(gs_ref_memory_t *, bool)); /* Initialize the save/restore machinery. */ void alloc_save_init(gs_dual_memory_t * dmem) { alloc_set_not_in_save(dmem); } /* Record that we are in a save. */ private void alloc_set_masks(gs_dual_memory_t *dmem, uint new_mask, uint test_mask) { int i; gs_ref_memory_t *mem; dmem->new_mask = new_mask; dmem->test_mask = test_mask; for (i = 0; i < countof(dmem->spaces.memories.indexed); ++i) if ((mem = dmem->spaces.memories.indexed[i]) != 0) { mem->new_mask = new_mask, mem->test_mask = test_mask; if (mem->stable_memory != (gs_memory_t *)mem) { mem = (gs_ref_memory_t *)mem->stable_memory; mem->new_mask = new_mask, mem->test_mask = test_mask; } } } void alloc_set_in_save(gs_dual_memory_t *dmem) { alloc_set_masks(dmem, l_new, l_new); } /* Record that we are not in a save. */ void alloc_set_not_in_save(gs_dual_memory_t *dmem) { alloc_set_masks(dmem, 0, ~0); } /* Save the state. */ private alloc_save_t *alloc_save_space(P3(gs_ref_memory_t *mem, gs_dual_memory_t *dmem, ulong sid)); private void alloc_free_save(gs_ref_memory_t *mem, alloc_save_t *save, const char *scn, const char *icn) { chunk_t *inner = mem->pcc; gs_free_object((gs_memory_t *)mem, save, scn); gs_free_object(mem->parent, inner, icn); } ulong alloc_save_state(gs_dual_memory_t * dmem, void *cdata) { gs_ref_memory_t *lmem = dmem->space_local; gs_ref_memory_t *gmem = dmem->space_global; ulong sid = gs_next_ids(2); bool global = lmem->save_level == 0 && gmem != lmem && gmem->num_contexts == 1; alloc_save_t *gsave = (global ? alloc_save_space(gmem, dmem, sid + 1) : (alloc_save_t *) 0); alloc_save_t *lsave = alloc_save_space(lmem, dmem, sid); if (lsave == 0 || (global &&gsave == 0)) { if (lsave != 0) alloc_free_save(lmem, lsave, "alloc_save_state(local save)", "alloc_save_state(local inner)"); if (gsave != 0) alloc_free_save(gmem, gsave, "alloc_save_state(global save)", "alloc_save_state(global inner)"); return 0; } if (gsave != 0) { gsave->client_data = 0; print_save("save", gmem->space, gsave); /* Restore names when we do the local restore. */ lsave->restore_names = gsave->restore_names; gsave->restore_names = false; } lsave->id = sid; lsave->client_data = cdata; print_save("save", lmem->space, lsave); /* Reset the l_new attribute in all slots. The only slots that */ /* can have the attribute set are the ones on the changes chain, */ /* and ones in objects allocated since the last save. */ if (lmem->save_level > 1) { long scanned = save_set_new(&lsave->state, false); if ((lsave->state.total_scanned += scanned) > max_repeated_scan) { /* Do a second, invisible save. */ alloc_save_t *rsave; rsave = alloc_save_space(lmem, dmem, 0L); if (rsave != 0) { rsave->client_data = cdata; rsave->id = lsave->id; print_save("save", lmem->space, rsave); lsave->id = 0; /* mark as invisible */ rsave->state.save_level--; /* ditto */ lsave->client_data = 0; /* Inherit the allocated space count -- */ /* we need this for triggering a GC. */ rsave->state.inherited = lsave->state.allocated + lsave->state.inherited; lmem->inherited = rsave->state.inherited; print_save("save", lmem->space, lsave); } } } alloc_set_in_save(dmem); return sid; } /* Save the state of one space (global or local). */ private alloc_save_t * alloc_save_space(gs_ref_memory_t * mem, gs_dual_memory_t * dmem, ulong sid) { gs_ref_memory_t save_mem; alloc_save_t *save; chunk_t *inner = 0; if (mem->cc.ctop - mem->cc.cbot > sizeof(chunk_head_t)) { inner = gs_raw_alloc_struct_immovable(mem->parent, &st_chunk, "alloc_save_space(inner)"); if (inner == 0) return 0; } save_mem = *mem; alloc_close_chunk(mem); gs_memory_status((gs_memory_t *) mem, &mem->previous_status); ialloc_reset(mem); mem->cc.cnext = mem->cc.cprev = 0; if (inner != 0) { /* Create an inner chunk to cover only the unallocated part. */ alloc_init_chunk(&mem->cc, mem->cc.cbot, mem->cc.ctop, true, mem->pcc); *inner = mem->cc; mem->pcc = inner; mem->cfirst = mem->clast = inner; } else { /* Not enough room to create an inner chunk. */ mem->pcc = 0; mem->cfirst = mem->clast = 0; mem->cc.cbot = mem->cc.ctop = 0; } save = gs_alloc_struct((gs_memory_t *) mem, alloc_save_t, &st_alloc_save, "alloc_save_space(save)"); #ifdef DEBUG if (inner != 0) { if_debug4('u', "[u]save space %u at 0x%lx: cbot=0x%lx ctop=0x%lx\n", mem->space, (ulong) save, (ulong) inner->cbot, (ulong) inner->ctop); } else { if_debug2('u', "[u]save space %u at 0x%lx (no inner)\n", mem->space, (ulong) save); } #endif if (save == 0) { gs_free_object(mem->parent, inner, "alloc_save_space(inner)"); *mem = save_mem; return 0; } save->state = save_mem; save->spaces = dmem->spaces; save->restore_names = (name_memory() == (gs_memory_t *) mem); save->is_current = (dmem->current == mem); save->id = sid; mem->saved = save; if_debug2('u', "[u%u]file_save 0x%lx\n", mem->space, (ulong) mem->streams); mem->streams = 0; mem->total_scanned = 0; if (sid) mem->save_level++; return save; } /* Record a state change that must be undone for restore, */ /* and mark it as having been saved. */ int alloc_save_change_in(gs_ref_memory_t *mem, const ref * pcont, ref_packed * where, client_name_t cname) { register alloc_change_t *cp; if (mem->new_mask == 0) return 0; /* no saving */ cp = gs_alloc_struct((gs_memory_t *)mem, alloc_change_t, &st_alloc_change, "alloc_save_change"); if (cp == 0) return -1; cp->next = mem->changes; cp->where = where; if (pcont == NULL) cp->offset = AC_OFFSET_STATIC; else if (r_is_array(pcont) || r_has_type(pcont, t_dictionary)) cp->offset = AC_OFFSET_REF; else if (r_is_struct(pcont)) cp->offset = (byte *) where - (byte *) pcont->value.pstruct; else { lprintf3("Bad type %u for save! pcont = 0x%lx, where = 0x%lx\n", r_type(pcont), (ulong) pcont, (ulong) where); gs_abort(); } if (r_is_packed(where)) *(ref_packed *)&cp->contents = *where; else { ref_assign_inline(&cp->contents, (ref *) where); r_set_attrs((ref *) where, l_new); } mem->changes = cp; #ifdef DEBUG if (gs_debug_c('U')) { dlprintf1("[U]save(%s)", client_name_string(cname)); alloc_save_print(cp, false); } #endif return 0; } int alloc_save_change(gs_dual_memory_t * dmem, const ref * pcont, ref_packed * where, client_name_t cname) { gs_ref_memory_t *mem = (pcont == NULL ? dmem->space_local : dmem->spaces_indexed[r_space(pcont) >> r_space_shift]); return alloc_save_change_in(mem, pcont, where, cname); } /* Return (the id of) the innermost externally visible save object, */ /* i.e., the innermost save with a non-zero ID. */ ulong alloc_save_current_id(const gs_dual_memory_t * dmem) { const alloc_save_t *save = dmem->space_local->saved; while (save != 0 && save->id == 0) save = save->state.saved; return save->id; } alloc_save_t * alloc_save_current(const gs_dual_memory_t * dmem) { return alloc_find_save(dmem, alloc_save_current_id(dmem)); } /* Test whether a reference would be invalidated by a restore. */ bool alloc_is_since_save(const void *vptr, const alloc_save_t * save) { /* A reference postdates a save iff it is in a chunk allocated */ /* since the save (including the carried-over inner chunk). */ const char *const ptr = (const char *)vptr; register const gs_ref_memory_t *mem = save->space_local; if_debug2('U', "[U]is_since_save 0x%lx, 0x%lx:\n", (ulong) ptr, (ulong) save); if (mem->saved == 0) { /* This is a special case, the final 'restore' from */ /* alloc_restore_all. */ return true; } /* Check against chunks allocated since the save. */ /* (There may have been intermediate saves as well.) */ for (;; mem = &mem->saved->state) { const chunk_t *cp; if_debug1('U', "[U]checking mem=0x%lx\n", (ulong) mem); for (cp = mem->cfirst; cp != 0; cp = cp->cnext) { if (ptr_is_within_chunk(ptr, cp)) { if_debug3('U', "[U+]in new chunk 0x%lx: 0x%lx, 0x%lx\n", (ulong) cp, (ulong) cp->cbase, (ulong) cp->cend); return true; } if_debug1('U', "[U-]not in 0x%lx\n", (ulong) cp); } if (mem->saved == save) { /* We've checked all the more recent saves, */ /* must be OK. */ break; } } /* * If we're about to do a global restore (save level = 1), * and there is only one context using this global VM * (the normal case, in which global VM is saved by the * outermost save), we also have to check the global save. * Global saves can't be nested, which makes things easy. */ if (mem->save_level == 1 && (mem = save->space_global) != save->space_local && save->space_global->num_contexts == 1 ) { const chunk_t *cp; if_debug1('U', "[U]checking global mem=0x%lx\n", (ulong) mem); for (cp = mem->cfirst; cp != 0; cp = cp->cnext) if (ptr_is_within_chunk(ptr, cp)) { if_debug3('U', "[U+] new chunk 0x%lx: 0x%lx, 0x%lx\n", (ulong) cp, (ulong) cp->cbase, (ulong) cp->cend); return true; } } return false; #undef ptr } /* Test whether a name would be invalidated by a restore. */ bool alloc_name_is_since_save(const ref * pnref, const alloc_save_t * save) { const name_string_t *pnstr; if (!save->restore_names) return false; pnstr = names_string_inline(the_gs_name_table, pnref); if (pnstr->foreign_string) return false; return alloc_is_since_save(pnstr->string_bytes, save); } bool alloc_name_index_is_since_save(uint nidx, const alloc_save_t * save) { ref nref; nref.value.pname = name_index_ptr(nidx); return alloc_name_is_since_save(&nref, save); } /* Check whether any names have been created since a given save */ /* that might be released by the restore. */ bool alloc_any_names_since_save(const alloc_save_t * save) { return save->restore_names; } /* Get the saved state with a given ID. */ alloc_save_t * alloc_find_save(const gs_dual_memory_t * dmem, ulong sid) { alloc_save_t *sprev = dmem->space_local->saved; if (sid == 0) return 0; /* invalid id */ while (sprev != 0) { if (sprev->id == sid) return sprev; sprev = sprev->state.saved; } return 0; } /* Get the client data from a saved state. */ void * alloc_save_client_data(const alloc_save_t * save) { return save->client_data; } /* * Do one step of restoring the state. The client is responsible for * calling alloc_find_save to get the save object, and for ensuring that * there are no surviving pointers for which alloc_is_since_save is true. * Return true if the argument was the innermost save, in which case * this is the last (or only) step. * Note that "one step" may involve multiple internal steps, * if this is the outermost restore (which requires restoring both local * and global VM) or if we created extra save levels to reduce scanning. */ private void restore_finalize(P1(gs_ref_memory_t *)); private void restore_space(P2(gs_ref_memory_t *, gs_dual_memory_t *)); bool alloc_restore_step_in(gs_dual_memory_t *dmem, alloc_save_t * save) { /* Get save->space_* now, because the save object will be freed. */ gs_ref_memory_t *lmem = save->space_local; gs_ref_memory_t *gmem = save->space_global; gs_ref_memory_t *mem = lmem; alloc_save_t *sprev; /* Finalize all objects before releasing resources or undoing changes. */ do { ulong sid; sprev = mem->saved; sid = sprev->id; restore_finalize(mem); /* finalize objects */ mem = &sprev->state; if (sid != 0) break; } while (sprev != save); if (mem->save_level == 0) { /* This is the outermost save, which might also */ /* need to restore global VM. */ mem = gmem; if (mem != lmem && mem->saved != 0) restore_finalize(mem); } /* Do one (externally visible) step of restoring the state. */ mem = lmem; do { ulong sid; sprev = mem->saved; sid = sprev->id; restore_resources(sprev, mem); /* release other resources */ restore_space(mem, dmem); /* release memory */ if (sid != 0) break; } while (sprev != save); if (mem->save_level == 0) { /* This is the outermost save, which might also */ /* need to restore global VM. */ mem = gmem; if (mem != lmem && mem->saved != 0) { restore_resources(mem->saved, mem); restore_space(mem, dmem); } alloc_set_not_in_save(dmem); } else { /* Set the l_new attribute in all slots that are now new. */ save_set_new(mem, true); } return sprev == save; } /* Restore the memory of one space, by undoing changes and freeing */ /* memory allocated since the save. */ private void restore_space(gs_ref_memory_t * mem, gs_dual_memory_t *dmem) { alloc_save_t *save = mem->saved; alloc_save_t saved; print_save("restore", mem->space, save); /* Undo changes since the save. */ { register alloc_change_t *cp = mem->changes; while (cp) { #ifdef DEBUG if (gs_debug_c('U')) { dlputs("[U]restore"); alloc_save_print(cp, true); } #endif if (r_is_packed(&cp->contents)) *cp->where = *(ref_packed *) & cp->contents; else ref_assign_inline((ref *) cp->where, &cp->contents); cp = cp->next; } } /* Free memory allocated since the save. */ /* Note that this frees all chunks except the inner one */ /* belonging to this level. */ saved = *save; restore_free(mem); /* Restore the allocator state. */ { int num_contexts = mem->num_contexts; /* don't restore */ *mem = saved.state; mem->num_contexts = num_contexts; } alloc_open_chunk(mem); /* Make the allocator current if it was current before the save. */ if (saved.is_current) { dmem->current = mem; dmem->current_space = mem->space; } } /* Restore to the initial state, releasing all resources. */ /* The allocator is no longer usable after calling this routine! */ void alloc_restore_all(gs_dual_memory_t * dmem) { /* * Save the memory pointers, since freeing space_local will also * free dmem itself. */ gs_ref_memory_t *lmem = dmem->space_local; gs_ref_memory_t *gmem = dmem->space_global; gs_ref_memory_t *smem = dmem->space_system; gs_ref_memory_t *mem; /* Restore to a state outside any saves. */ while (lmem->save_level != 0) discard(alloc_restore_step_in(dmem, lmem->saved)); /* Finalize memory. */ restore_finalize(lmem); if ((mem = (gs_ref_memory_t *)lmem->stable_memory) != lmem) restore_finalize(mem); if (gmem != lmem && gmem->num_contexts == 1) { restore_finalize(gmem); if ((mem = (gs_ref_memory_t *)gmem->stable_memory) != gmem) restore_finalize(mem); } restore_finalize(smem); /* Release resources other than memory, using fake */ /* save and memory objects. */ { alloc_save_t empty_save; empty_save.spaces = dmem->spaces; empty_save.restore_names = false; /* don't bother to release */ restore_resources(&empty_save, NULL); } /* Finally, release memory. */ restore_free(lmem); if ((mem = (gs_ref_memory_t *)lmem->stable_memory) != lmem) restore_free(mem); if (gmem != lmem) { if (!--(gmem->num_contexts)) { restore_free(gmem); if ((mem = (gs_ref_memory_t *)gmem->stable_memory) != gmem) restore_free(mem); } } restore_free(smem); } /* * Finalize objects that will be freed by a restore. * Note that we must temporarily disable the freeing operations * of the allocator while doing this. */ private void restore_finalize(gs_ref_memory_t * mem) { chunk_t *cp; alloc_close_chunk(mem); gs_enable_free((gs_memory_t *) mem, false); for (cp = mem->clast; cp != 0; cp = cp->cprev) { SCAN_CHUNK_OBJECTS(cp) DO_ALL struct_proc_finalize((*finalize)) = pre->o_type->finalize; if (finalize != 0) { if_debug2('u', "[u]restore finalizing %s 0x%lx\n", struct_type_name_string(pre->o_type), (ulong) (pre + 1)); (*finalize) (pre + 1); } END_OBJECTS_SCAN } gs_enable_free((gs_memory_t *) mem, true); } /* Release resources for a restore */ private void restore_resources(alloc_save_t * sprev, gs_ref_memory_t * mem) { #ifdef DEBUG if (mem) { /* Note restoring of the file list. */ if_debug4('u', "[u%u]file_restore 0x%lx => 0x%lx for 0x%lx\n", mem->space, (ulong)mem->streams, (ulong)sprev->state.streams, (ulong) sprev); } #endif /* Remove entries from font and character caches. */ font_restore(sprev); /* Adjust the name table. */ if (sprev->restore_names) names_restore(the_gs_name_table, sprev); } /* Release memory for a restore. */ private void restore_free(gs_ref_memory_t * mem) { /* Free chunks allocated since the save. */ gs_free_all((gs_memory_t *) mem); } /* Forget a save, by merging this level with the next outer one. */ private void file_forget_save(P1(gs_ref_memory_t *)); private void combine_space(P1(gs_ref_memory_t *)); private void forget_changes(P1(gs_ref_memory_t *)); void alloc_forget_save_in(gs_dual_memory_t *dmem, alloc_save_t * save) { gs_ref_memory_t *mem = save->space_local; alloc_save_t *sprev; print_save("forget_save", mem->space, save); /* Iteratively combine the current level with the previous one. */ do { sprev = mem->saved; if (sprev->id != 0) mem->save_level--; if (mem->save_level != 0) { alloc_change_t *chp = mem->changes; save_set_new(&sprev->state, true); /* Concatenate the changes chains. */ if (chp == 0) mem->changes = sprev->state.changes; else { while (chp->next != 0) chp = chp->next; chp->next = sprev->state.changes; } file_forget_save(mem); combine_space(mem); /* combine memory */ } else { forget_changes(mem); save_set_new(mem, false); file_forget_save(mem); combine_space(mem); /* combine memory */ /* This is the outermost save, which might also */ /* need to combine global VM. */ mem = save->space_global; if (mem != save->space_local && mem->saved != 0) { forget_changes(mem); save_set_new(mem, false); file_forget_save(mem); combine_space(mem); } alloc_set_not_in_save(dmem); break; /* must be outermost */ } } while (sprev != save); } /* Combine the chunks of the next outer level with those of the current one, */ /* and free the bookkeeping structures. */ private void combine_space(gs_ref_memory_t * mem) { alloc_save_t *saved = mem->saved; gs_ref_memory_t *omem = &saved->state; chunk_t *cp; chunk_t *csucc; alloc_close_chunk(mem); for (cp = mem->cfirst; cp != 0; cp = csucc) { csucc = cp->cnext; /* save before relinking */ if (cp->outer == 0) alloc_link_chunk(cp, omem); else { chunk_t *outer = cp->outer; outer->inner_count--; if (mem->pcc == cp) mem->pcc = outer; if (mem->cfreed.cp == cp) mem->cfreed.cp = outer; /* "Free" the header of the inner chunk, */ /* and any immediately preceding gap left by */ /* the GC having compacted the outer chunk. */ { obj_header_t *hp = (obj_header_t *) outer->cbot; hp->o_alone = 0; hp->o_size = (char *)(cp->chead + 1) - (char *)(hp + 1); hp->o_type = &st_bytes; /* The following call is probably not safe. */ #if 0 /* **************** */ gs_free_object((gs_memory_t *) mem, hp + 1, "combine_space(header)"); #endif /* **************** */ } /* Update the outer chunk's allocation pointers. */ outer->cbot = cp->cbot; outer->rcur = cp->rcur; outer->rtop = cp->rtop; outer->ctop = cp->ctop; outer->has_refs |= cp->has_refs; gs_free_object(mem->parent, cp, "combine_space(inner)"); } } /* Update relevant parts of allocator state. */ mem->cfirst = omem->cfirst; mem->clast = omem->clast; mem->allocated += omem->allocated; mem->gc_allocated += omem->allocated; mem->lost.objects += omem->lost.objects; mem->lost.refs += omem->lost.refs; mem->lost.strings += omem->lost.strings; mem->saved = omem->saved; mem->previous_status = omem->previous_status; { /* Concatenate free lists. */ int i; for (i = 0; i < num_freelists; i++) { obj_header_t *olist = omem->freelists[i]; obj_header_t *list = mem->freelists[i]; if (olist == 0); else if (list == 0) mem->freelists[i] = olist; else { while (*(obj_header_t **) list != 0) list = *(obj_header_t **) list; *(obj_header_t **) list = olist; } } if (omem->largest_free_size > mem->largest_free_size) mem->largest_free_size = omem->largest_free_size; } gs_free_object((gs_memory_t *) mem, saved, "combine_space(saved)"); alloc_open_chunk(mem); } /* Free the changes chain for a level 0 .forgetsave, */ /* resetting the l_new flag in the changed refs. */ private void forget_changes(gs_ref_memory_t * mem) { register alloc_change_t *chp = mem->changes; alloc_change_t *next; for (; chp; chp = next) { ref_packed *prp = chp->where; if_debug1('U', "[U]forgetting change 0x%lx\n", (ulong) chp); if (!r_is_packed(prp)) r_clear_attrs((ref *) prp, l_new); next = chp->next; gs_free_object((gs_memory_t *) mem, chp, "forget_changes"); } mem->changes = 0; } /* Update the streams list when forgetting a save. */ private void file_forget_save(gs_ref_memory_t * mem) { const alloc_save_t *save = mem->saved; stream *streams = mem->streams; stream *saved_streams = save->state.streams; if_debug4('u', "[u%d]file_forget_save 0x%lx + 0x%lx for 0x%lx\n", mem->space, (ulong) streams, (ulong) saved_streams, (ulong) save); if (streams == 0) mem->streams = saved_streams; else if (saved_streams != 0) { while (streams->next != 0) streams = streams->next; streams->next = saved_streams; saved_streams->prev = streams; } } /* ------ Internal routines ------ */ /* Set or reset the l_new attribute in every relevant slot. */ /* This includes every slot on the current change chain, */ /* and every (ref) slot allocated at this save level. */ /* Return the number of bytes of data scanned. */ private long save_set_new(gs_ref_memory_t * mem, bool to_new) { long scanned = 0; /* Handle the change chain. */ save_set_new_changes(mem, to_new); /* Handle newly allocated ref objects. */ SCAN_MEM_CHUNKS(mem, cp) { if (cp->has_refs) { bool has_refs = false; SCAN_CHUNK_OBJECTS(cp) DO_ALL if_debug3('U', "[U]set_new scan(0x%lx(%u), %d)\n", (ulong) pre, size, to_new); if (pre->o_type == &st_refs) { /* These are refs, scan them. */ ref_packed *prp = (ref_packed *) (pre + 1); ref_packed *next = (ref_packed *) ((char *)prp + size); #ifdef ALIGNMENT_ALIASING_BUG ref *rpref; # define RP_REF(rp) (rpref = (ref *)rp, rpref) #else # define RP_REF(rp) ((ref *)rp) #endif if_debug2('U', "[U]refs 0x%lx to 0x%lx\n", (ulong) prp, (ulong) next); has_refs = true; scanned += size; /* We know that every block of refs ends with */ /* a full-size ref, so we only need the end check */ /* when we encounter one of those. */ if (to_new) while (1) { if (r_is_packed(prp)) prp++; else { RP_REF(prp)->tas.type_attrs |= l_new; prp += packed_per_ref; if (prp >= next) break; } } else while (1) { if (r_is_packed(prp)) prp++; else { RP_REF(prp)->tas.type_attrs &= ~l_new; prp += packed_per_ref; if (prp >= next) break; } } #undef RP_REF } else scanned += sizeof(obj_header_t); END_OBJECTS_SCAN cp->has_refs = has_refs; } } END_CHUNKS_SCAN if_debug2('u', "[u]set_new (%s) scanned %ld\n", (to_new ? "restore" : "save"), scanned); return scanned; } /* Set or reset the l_new attribute on the changes chain. */ private void save_set_new_changes(gs_ref_memory_t * mem, bool to_new) { register alloc_change_t *chp = mem->changes; register uint new = (to_new ? l_new : 0); for (; chp; chp = chp->next) { ref_packed *prp = chp->where; if_debug3('U', "[U]set_new 0x%lx: (0x%lx, %d)\n", (ulong)chp, (ulong)prp, new); if (!r_is_packed(prp)) { ref *const rp = (ref *) prp; rp->tas.type_attrs = (rp->tas.type_attrs & ~l_new) + new; } } }