/* Copyright (C) 1991, 1992, 1993 Aladdin Enterprises. All rights reserved. This file is part of Ghostscript. Ghostscript is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. No author or distributor accepts responsibility to anyone for the consequences of using it or for whether it serves any particular purpose or works at all, unless he says so in writing. Refer to the Ghostscript General Public License for full details. Everyone is granted permission to copy, modify and redistribute Ghostscript, but only under the conditions described in the Ghostscript General Public License. A copy of this license is supposed to have been given to you along with Ghostscript so you can know your rights and responsibilities. It should be in a file named COPYING. Among other things, the copyright notice and this notice must be preserved on all copies. */ /* isave.c */ /* Save/restore machinery for Ghostscript */ #include "ghost.h" #include "memory_.h" #include "alloc.h" #include "astate.h" #include "errors.h" #include "iname.h" #include "packed.h" #include "save.h" #include "store.h" /* for ref_assign */ /* Imported restore routines */ extern void file_save(P0()); extern void file_restore(P1(alloc_save *)); extern void font_restore(P1(alloc_save *)); /* * The logic for saving and restore the state is rather subtle. * 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. * Save the identity of the current chunk. * Save and reset the malloc chain and the orphan 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. * Free all malloc'ed blocks newer than the save. * Make current the chunk that was current at the time of the save. * Free all objects allocated in the current chunk since the save. */ /* * For saving changes to individual objects, we add an "attribute" bit * (l_new) that logically belongs to the slot where the descriptor is stored, * not to the descriptor itself. The bit means "the contents * of this slot have been changed 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 the bit in all slots. * * When storing into a slot, if the save level is non-zero: * If the bit isn't set, save the address and contents of the slot * on the current contents chain. * Set the bit after storing the new value. * * To do a save: * If the save level is non-zero: * Reset the bit in all slots on the contents chain, and in all * objects created since the previous save. * Push the head of 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 the bit in all * the slots it references. * Scan all objects created since the previous save, and set the * bit in all the slots of each object. */ /* Declare the mask for checking stores. */ /* This is -1 if we are not in a save, 0 if we are in a save. */ int alloc_save_test_mask; /* Declare the mask for tagging new objects. */ /* This is 0 if we are not in a save, l_new if we are in a save. */ int alloc_save_new_mask; #define set_in_save()\ (alloc_save_test_mask = 0, alloc_save_new_mask = l_new) #define set_not_in_save()\ (alloc_save_test_mask = -1, alloc_save_new_mask = 0) /* Structure for saved change chain for save/restore. */ /* If where = 0, contents is a t_array that refers to */ /* a newly allocated object; if where = 0 and contents.value.refs is 0, */ /* this is a free record (possible since we allocate them two at a time.) */ /* We merge adjacent objects to reduce */ /* the need to allocate alloc_change records. */ struct alloc_change_s { alloc_change *next; ref *where; ref contents; }; #define alloc_change_is_free(cp)\ ((cp)->where == 0 && r_size(&(cp)->contents) == 0) /* * Macro to allocate a pair of change records. * Must be used in the following form: * if_not_alloc_change_pair(cp, ap, cname) * { ... failure code ... * } */ #define if_not_alloc_change_pair(cp, ap, cname)\ cp = (alloc_change *)alloc(2, sizeof(alloc_change), cname);\ if ( cp != 0 )\ { cp->next = ap->changes;\ cp[1].next = cp;\ cp[1].where = 0;\ cp[1].contents.value.refs = 0;\ r_set_size(&cp[1].contents, 0);\ ap->changes = cp + 1;\ }\ else /* Saved state of allocator and other things as needed. */ struct alloc_save_s { alloc_state state; alloc_state_ptr cap; uint name_cnt; }; /* Debugging printout */ #ifdef DEBUG private void alloc_save_print(alloc_change *cp) { dprintf1(" %lx:", (ulong)cp); if ( cp->where ) dprintf4(" %lx: %x %x %lx\n", (ulong)cp->where, r_type_attrs(&cp->contents), r_size(&cp->contents), (ulong)cp->contents.value.intval); else dprintf2(" %lx(%u)\n", (ulong)cp->contents.value.refs, r_size(&cp->contents)); } #endif /* Forward references */ private void restore_resources(P1(alloc_save *)); private void restore_free(P1(alloc_state_ptr)); private void save_set_new(P2(alloc_state_ptr, int)); /* Initialize the save/restore machinery. */ void alloc_save_init(void) { set_not_in_save(); } /* Save the state. */ alloc_save * alloc_save_state(void) { register alloc_state_ptr ap = alloc_state_current; alloc_save *save; save = (alloc_save *)alloc(1, sizeof(alloc_save), "alloc_save_state"); if ( save == 0 ) return 0; save->state = *ap; save->cap = ap; save->name_cnt = name_count(); /* Reset the l_new attribute in all slots. The only slots that */ /* can have the attribute set are the ones on the changes chain. */ save_set_new(ap, 0); /* Clear the free chains, to prevent old objects from being freed. */ memset(&ap->free[0], 0, num_free_chains * sizeof(char *)); ap->malloc_chain = 0; ap->saved = save; ap->save_level++; ap->changes = 0; ap->saved_cbot = ap->cbot; ap->saved_ctop = ap->ctop; /* Clear the last_freed cache, because the cache pointer */ /* must point to a chunk at the current save level. */ ap->last_freed = 0; if_debug3('u', "[u]save at %lx: cbot=%lx ctop=%lx\n", (ulong)save, (ulong)ap->cbot, (ulong)ap->ctop); set_in_save(); /* Notify the file machinery we just did a save. */ file_save(); return save; } /* Allocate an array of refs and record it as new. */ int alloc_array(ref *parr, uint attrs, uint num_refs, const char *client_name) { register alloc_state_ptr ap = alloc_state_current; register ref *obj = (ref *)alloc(num_refs, sizeof(ref), client_name); if ( obj == 0 ) return_error(e_VMerror); if ( ap->save_level != 0 && num_refs != 0 ) { /* We are saving, record the allocation. */ register alloc_change *cp = ap->changes; if ( cp != 0 && cp->where == 0 && obj == cp->contents.value.refs + r_size(&cp->contents) && /* Don't create a single block that is large enough */ /* to mislead the allocator into thinking it was */ /* allocated with a single malloc. */ r_size(&cp->contents) + num_refs < ap->big_size / sizeof(ref) ) { /* Merge adjacent allocations. */ r_inc_size(&cp->contents, num_refs); #ifdef DEBUG if ( gs_debug['U'] ) { dprintf1("[u]alloc_array(%s) merge", client_name); alloc_save_print(cp); } #endif } else { if ( cp == 0 || !alloc_change_is_free(cp) ) { /* Allocate a pair of entries. */ if_not_alloc_change_pair(cp, ap, "alloc_array(change pair)") { alloc_free((char *)obj, num_refs, sizeof(ref), client_name); return_error(e_VMerror); } } cp->where = 0; r_set_size(&cp->contents, num_refs); cp->contents.value.refs = obj; #ifdef DEBUG if ( gs_debug['U'] ) { dprintf1("[u]alloc_array(%s)", client_name); alloc_save_print(cp); } #endif } } make_array(parr, attrs | ap->local_attr, num_refs, obj); return 0; } /* Deallocate an array of refs. Only do this if the object */ /* was allocated at the current save level, and we can remove */ /* the save record for the allocation cleanly. */ void alloc_free_array(ref *parr, const char *client_name) { ref *ptr = parr->value.refs; uint num_refs = r_size(parr); register alloc_state_ptr ap = alloc_state_current; alloc_change *cp = ap->changes; ref *top = ptr + num_refs; if_debug3('U', "[u]alloc_free_array(%s) (%lx,%d)\n", client_name, (ulong)ptr, num_refs); if ( num_refs == 0 ) /* may point anywhere! */ return; if ( ap->save_level == 0 ) { /* Always OK to free if not saving. */ if_debug0('U', "[u]... not saving\n"); alloc_free((char *)ptr, num_refs, sizeof(ref), client_name); return; } /* Search the save chain for the allocation event. */ while ( cp != 0 ) { ref *rbot; if ( cp->where == 0 && ((rbot = cp->contents.value.refs) == ptr || rbot + r_size(&cp->contents) == top) ) { /* We can undo the allocation record cleanly. */ if_debug0('U', "[u]... succeeded\n"); r_inc_size(&cp->contents, -num_refs); if ( rbot == ptr ) cp->contents.value.refs = top; alloc_free((char *)ptr, num_refs, sizeof(ref), client_name); break; } cp = cp->next; } } /* Record a state change that must be undone for restore, */ /* and mark it as having been saved. */ /* This can only be called if we are in a save. */ int alloc_save_change(ref *where, const char *client_name) { register alloc_state_ptr ap = alloc_state_current; register alloc_change *cp; if ( ap->save_level == 0 ) return 0; /* no saving */ cp = ap->changes; if ( cp == 0 || !alloc_change_is_free(cp) ) { /* Allocate a pair of entries. */ if_not_alloc_change_pair(cp, ap, "alloc_save_change(change pair)") { return -1; } } cp->where = where; ref_assign(&cp->contents, where); #ifdef DEBUG if ( gs_debug['U'] ) { dprintf1("[u]save(%s)", client_name); alloc_save_print(cp); } #endif if ( !r_is_packed(where) ) r_set_attrs(where, l_new); return 0; } /* Return the current save level */ int alloc_save_level(void) { return alloc_state_current->save_level; } /* Test whether a reference would be invalidated by a restore. */ int alloc_is_since_save(const char *ptr, const alloc_save *save) { /* A reference can postdate a save in one of three ways: */ /* - It is in the chunk that was current at the time */ /* of the save, and allocated more recently. */ /* - It is in a chunk allocated since the save; */ /* - It was malloc'ed since the save; */ register alloc_state_ptr ap = save->cap; if_debug2('U', "[U]is_since_save %lx, %lx:\n", (ulong)ptr, (ulong)save); /* Check against current chunk at the time of the save */ if ( ptr_is_in_chunk(ptr, &save->state.current) ) { /* In the chunk, check against allocation pointers */ /* at the time of the save */ if_debug2('U', "[U?] current chunk %lx, %lx\n", (ulong)save->state.cbot, (ulong)save->state.ctop); return ( (ptr_ord_t)ptr >= (ptr_ord_t)save->state.cbot && (ptr_ord_t)ptr < (ptr_ord_t)save->state.ctop ); } /* Check against chunks allocated since the save */ { const alloc_chunk *chunk = &ap->current; while ( chunk->save_level > save->state.save_level ) { if ( ptr_is_in_chunk(ptr, chunk) ) { if_debug3('U', "[U+] new chunk %lx: %lx, %lx\n", chunk, (ulong)chunk->base, (ulong)chunk->limit); return 1; } chunk = chunk->next; } } /* Check the malloc chains since the save */ { const alloc_state *asp = ap; for ( ; asp != &save->state; asp = &asp->saved->state ) { const alloc_block *mblk = asp->malloc_chain; for ( ; mblk != 0; mblk = mblk->next ) if ( alloc_block_size + (char *)mblk == ptr ) { if_debug0('U', "[U+] malloc'ed\n"); return 1; } } } /* Not in any of those places, must be OK. */ return 0; } /* Test whether a name would be invalidated by a restore. */ int alloc_name_is_since_save(const ref *pnref, const alloc_save *save) { return name_is_since_count(pnref, save->name_cnt); } /* Validate a saved state pointer. */ int alloc_restore_state_check(const alloc_save *save) { const alloc_save *sprev = save->cap->saved; while ( sprev != save ) { if ( sprev == 0 ) return -1; /* not on chain */ sprev = sprev->state.saved; } return 0; } /* Restore the state. The client is responsible for calling */ /* alloc_restore_state_check first, and for ensuring that */ /* there are no surviving pointers for which alloc_is_since_save is true. */ void alloc_restore_state(alloc_save *save) { register alloc_state_ptr ap = save->cap; alloc_save *sprev; if_debug1('u', "[u]restore from %lx\n", (ulong)save); /* Iteratively restore the state */ do { sprev = ap->saved; /* Release resources other than memory */ restore_resources(sprev); /* Undo changes since the save. */ { alloc_change *cp = ap->changes; while ( cp ) { #ifdef DEBUG if ( gs_debug['U'] ) { dprintf("[U]restore"); alloc_save_print(cp); } #endif if ( cp->where ) ref_assign(cp->where, &cp->contents); else if ( r_size(&cp->contents) != 0 ) /* might be an unfilled save record */ { alloc_free((char *)cp->contents.value.refs, r_size(&cp->contents), sizeof(ref), "alloc_restore_state"); } cp = cp->next; } } /* Free memory allocated since the save. */ restore_free(ap); /* Restore the allocator state. */ *ap = sprev->state; alloc_free((char *)sprev, 1, sizeof(alloc_save), "alloc_restore_state(alloc_save)"); } while ( sprev != save ); /* Clean up */ sprev = ap->saved; if ( sprev == 0 ) ap->saved_cbot = ap->saved_ctop = 0; else ap->saved_cbot = sprev->state.cbot, ap->saved_ctop = sprev->state.ctop; /* Clear the last_freed cache, because the cache pointer */ /* must point to a chunk at the current save level. */ ap->last_freed = 0; if ( ap->save_level == 0 ) set_not_in_save(); /* Set the l_new attribute in all slots that have been saved. */ save_set_new(ap, l_new); } /* Restore to the initial state, releasing all resources. */ /* The allocator is no longer usable after calling this routine! */ void alloc_restore_all(void) { register alloc_state_ptr ap = alloc_state_current; /* Restore to a state outside any saves. */ while ( ap->saved != 0 ) alloc_restore_state(ap->saved); /* Release resources other than memory, using */ /* a fake save object that has no associated memory. */ { alloc_save empty_save; #if arch_ptrs_are_signed # define min_ptr (-1L << (sizeof(char *) * 8 - 1)) #else # define min_ptr 0L #endif #define max_ptr (~min_ptr) empty_save.state.current.base = empty_save.state.cbot = (byte *)min_ptr; empty_save.state.current.limit = empty_save.state.ctop = (byte *)max_ptr; empty_save.name_cnt = name_count(); /* don't bother to release */ restore_resources(&empty_save); } /* Finally, release memory. */ restore_free(ap); } /* Release resources for a restore */ private void restore_resources(alloc_save *sprev) { /* Close inaccessible files. */ file_restore(sprev); /* Remove entries from font and character caches. */ font_restore(sprev); /* Adjust the name table. */ name_restore(sprev->name_cnt); } /* Release memory for a restore. */ private void restore_free(alloc_state_ptr ap) { /* Free chunks allocated since the save. */ { alloc_chunk *cp = ap->current_ptr; *cp = ap->current; /* update in memory */ } while ( ap->current.save_level == ap->save_level ) { byte *cp = (byte *)ap->current_ptr; uint csize = ap->climit - cp; ap->current_ptr = ap->current.next; if ( ap->current_ptr != 0 ) ap->current = *ap->current_ptr; else /* only possible from restore_all */ ap->current.save_level--; (*ap->mprocs->free)((char *)cp, 1, csize, "alloc_restore_state(chunk)"); } /* Free blocks allocated with malloc since the save. */ /* Since we reset the chain when we did the save, */ /* we just free all the objects on the current chain. */ { while ( ap->malloc_chain != 0 ) { alloc_block *mblock = ap->malloc_chain; ap->malloc_chain = mblock->next; (*ap->mprocs->free)((char *)mblock, 1, alloc_block_size + mblock->size, "alloc_restore_state(malloc'ed)"); } } } /* ------ Internal routines ------ */ /* Set or reset the l_new attribute in every slot on the current */ /* change chain. */ private void save_set_new(alloc_state_ptr ap, int new) /* l_new or 0 */ { register alloc_change *cp = ap->changes; for ( ; cp; cp = cp->next ) { ref *rp = cp->where; if ( rp != 0 ) { if ( !r_is_packed(rp) ) rp->tas.type_attrs = (rp->tas.type_attrs & ~l_new) + new; } else { register ushort size = r_size(&cp->contents); if ( size ) { register ref *ep = cp->contents.value.refs; if ( new ) do { if ( !r_is_packed(ep) ) ep->tas.type_attrs |= l_new; ep++; } while ( --size ); else do { if ( !r_is_packed(ep) ) ep->tas.type_attrs &= ~l_new; ep++; } while ( --size ); } } } }