diff options
author | Henry Stiles <henry.stiles@artifex.com> | 1998-07-26 07:36:41 +0000 |
---|---|---|
committer | Henry Stiles <henry.stiles@artifex.com> | 1998-07-26 07:36:41 +0000 |
commit | eec0ef527f18c5978c4476c9490f4de4c4249628 (patch) | |
tree | 5588d5e1300a245186594893c930949a19bcbbce /gs/src/gsalloc.c | |
parent | d4bdba93ef34f68d27148e1b31088d1d3e786e8c (diff) |
Initial revision
git-svn-id: http://svn.ghostscript.com/ghostpcl/trunk/ghostpcl@246 06663e23-700e-0410-b217-a244a6096597
Diffstat (limited to 'gs/src/gsalloc.c')
-rw-r--r-- | gs/src/gsalloc.c | 1282 |
1 files changed, 1282 insertions, 0 deletions
diff --git a/gs/src/gsalloc.c b/gs/src/gsalloc.c new file mode 100644 index 000000000..6e872d623 --- /dev/null +++ b/gs/src/gsalloc.c @@ -0,0 +1,1282 @@ +/* Copyright (C) 1995, 1996, 1997 Aladdin Enterprises. All rights reserved. + + This file is part of Aladdin Ghostscript. + + Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND. No author + or distributor accepts any responsibility for the consequences of using it, + or for whether it serves any particular purpose or works at all, unless he + or she says so in writing. Refer to the Aladdin Ghostscript Free Public + License (the "License") for full details. + + Every copy of Aladdin Ghostscript must include a copy of the License, + normally in a plain ASCII text file named PUBLIC. The License grants you + the right to copy, modify and redistribute Aladdin Ghostscript, but only + under certain conditions described in the License. Among other things, the + License requires that the copyright notice and this notice be preserved on + all copies. +*/ + +/* gsalloc.c */ +/* Standard memory allocator */ +#include "gx.h" +#include "memory_.h" +#include "gsmdebug.h" +#include "gsstruct.h" +#include "gxalloc.h" + +/* + * This allocator produces tracing messages of the form + * [aNMOTS]... + * where + * N is the VM space number, + * M is : for movable objects, | for immovable, + * O is {alloc = +, free = -, grow = >, shrink = <}, + * T is {bytes = b, object = <, ref = $, string = >}, and + * S is {freelist = F, LIFO = space, own chunk = L, lost = #, + * lost own chunk = ~, other = .}. + */ + +/* The structure descriptor for allocators. Even though allocators */ +/* are allocated outside GC space, they reference objects within it. */ +public_st_ref_memory(); +#define mptr ((gs_ref_memory_t *)vptr) +private ENUM_PTRS_BEGIN(ref_memory_enum_ptrs) return 0; + ENUM_PTR(0, gs_ref_memory_t, changes); + ENUM_PTR(1, gs_ref_memory_t, saved); +ENUM_PTRS_END +private RELOC_PTRS_BEGIN(ref_memory_reloc_ptrs) { + RELOC_PTR(gs_ref_memory_t, changes); + /* Don't relocate the pointer now -- see igc.c for details. */ + mptr->reloc_saved = gs_reloc_struct_ptr(mptr->saved, gcst); +} RELOC_PTRS_END + +/* Forward references */ +private ulong compute_free_objects(P1(gs_ref_memory_t *)); +private obj_header_t *alloc_obj(P5(gs_ref_memory_t *, ulong, gs_memory_type_ptr_t, bool, client_name_t)); +private chunk_t *alloc_add_chunk(P4(gs_ref_memory_t *, ulong, bool, client_name_t)); +void alloc_close_chunk(P1(gs_ref_memory_t *)); + +#define imem ((gs_ref_memory_t *)mem) + +/* + * Define the standard implementation (with garbage collection) + * of Ghostscript's memory manager interface. + */ +private gs_memory_proc_alloc_bytes(i_alloc_bytes); +private gs_memory_proc_alloc_bytes(i_alloc_bytes_immovable); +private gs_memory_proc_alloc_struct(i_alloc_struct); +private gs_memory_proc_alloc_struct(i_alloc_struct_immovable); +private gs_memory_proc_alloc_byte_array(i_alloc_byte_array); +private gs_memory_proc_alloc_byte_array(i_alloc_byte_array_immovable); +private gs_memory_proc_alloc_struct_array(i_alloc_struct_array); +private gs_memory_proc_alloc_struct_array(i_alloc_struct_array_immovable); +private gs_memory_proc_resize_object(i_resize_object); +private gs_memory_proc_object_size(i_object_size); +private gs_memory_proc_object_type(i_object_type); +private gs_memory_proc_free_object(i_free_object); +private gs_memory_proc_alloc_string(i_alloc_string); +private gs_memory_proc_alloc_string(i_alloc_string_immovable); +private gs_memory_proc_resize_string(i_resize_string); +private gs_memory_proc_free_string(i_free_string); +private gs_memory_proc_register_root(i_register_root); +private gs_memory_proc_unregister_root(i_unregister_root); +private gs_memory_proc_status(i_status); +private gs_memory_proc_enable_free(i_enable_free); +/* We export the procedures for subclasses. */ +const gs_memory_procs_t gs_ref_memory_procs = { + i_alloc_bytes, + i_alloc_bytes_immovable, + i_alloc_struct, + i_alloc_struct_immovable, + i_alloc_byte_array, + i_alloc_byte_array_immovable, + i_alloc_struct_array, + i_alloc_struct_array_immovable, + i_resize_object, + i_object_size, + i_object_type, + i_free_object, + i_alloc_string, + i_alloc_string_immovable, + i_resize_string, + i_free_string, + i_register_root, + i_unregister_root, + i_status, + i_enable_free +}; +/* + * Allocate and mostly initialize the state of an allocator (system, global, + * or local). Does not initialize global or space. + */ +private void *ialloc_solo(P3(gs_memory_t *, gs_memory_type_ptr_t, chunk_t **)); +gs_ref_memory_t * +ialloc_alloc_state(gs_memory_t *parent, uint chunk_size) +{ chunk_t *cp; + gs_ref_memory_t *iimem = ialloc_solo(parent, &st_ref_memory, &cp); + + if ( iimem == 0 ) + return 0; + iimem->procs = gs_ref_memory_procs; + iimem->parent = parent; + iimem->chunk_size = chunk_size; + iimem->large_size = ((chunk_size / 4) & -obj_align_mod) + 1; + iimem->gc_status.vm_threshold = chunk_size * 3L; + iimem->gc_status.max_vm = max_long; + iimem->gc_status.psignal = NULL; + iimem->gc_status.enabled = false; + iimem->previous_status.allocated = 0; + iimem->previous_status.used = 0; + ialloc_reset(iimem); + iimem->cfirst = iimem->clast = cp; + ialloc_set_limit(iimem); + iimem->cc.cbot = iimem->cc.ctop = 0; + iimem->pcc = 0; + iimem->roots = 0; + iimem->num_contexts = 1; + iimem->saved = 0; + return iimem; +} +/* Allocate a 'solo' object with its own chunk. */ +private void * +ialloc_solo(gs_memory_t *parent, gs_memory_type_ptr_t pstype, chunk_t **pcp) +{ /* + * We can't assume that the parent uses the same object header + * that we do, but the GC requires that allocators have + * such a header. Therefore, we prepend one explicitly. + */ + chunk_t *cp = gs_alloc_struct_immovable(parent, chunk_t, &st_chunk, + "ialloc_solo(chunk)"); + uint csize = + round_up(sizeof(chunk_head_t) + sizeof(obj_header_t) + + pstype->ssize, + obj_align_mod); + byte *cdata = gs_alloc_bytes_immovable(parent, csize, "ialloc_solo"); + obj_header_t *obj = (obj_header_t *)(cdata + sizeof(chunk_head_t)); + + if ( cp == 0 || cdata == 0 ) + return 0; + alloc_init_chunk(cp, cdata, cdata + csize, false, (chunk_t *)NULL); + cp->cbot = cp->ctop; + cp->cprev = cp->cnext = 0; + /* Construct the object header "by hand". */ + obj->o_large = 0; + obj->o_size = pstype->ssize; + obj->o_type = pstype; + *pcp = cp; + return (void *)(obj + 1); +} +/* Initialize after a save. */ +void +ialloc_reset(gs_ref_memory_t *mem) +{ mem->cfirst = 0; + mem->clast = 0; + mem->cc.rcur = 0; + mem->cc.rtop = 0; + mem->cc.has_refs = false; + mem->allocated = 0; + mem->inherited = 0; + mem->changes = 0; + ialloc_reset_free(mem); +} +/* Initialize after a save or GC. */ +void +ialloc_reset_free(gs_ref_memory_t *mem) +{ int i; + obj_header_t **p; + mem->lost.objects = 0; + mem->lost.refs = 0; + mem->lost.strings = 0; + mem->cfreed.cp = 0; + for ( i = 0, p = &mem->freelists[0]; i < num_freelists; i++, p++ ) + *p = 0; +} +/* Set the allocation limit after a change in one or more of */ +/* vm_threshold, max_vm, or enabled, or after a GC. */ +void +ialloc_set_limit(register gs_ref_memory_t *mem) +{ /* + * The following code is intended to set the limit so that + * we stop allocating when allocated + previous_status.allocated + * exceeds the lesser of max_vm or (if GC is enabled) + * gc_allocated + vm_threshold. + */ + ulong max_allocated = + (mem->gc_status.max_vm > mem->previous_status.allocated ? + mem->gc_status.max_vm - mem->previous_status.allocated : + 0); + if ( mem->gc_status.enabled ) + { ulong limit = mem->gc_allocated + mem->gc_status.vm_threshold; + if ( limit < mem->previous_status.allocated ) + mem->limit = 0; + else + { limit -= mem->previous_status.allocated; + mem->limit = min(limit, max_allocated); + } + } + else + mem->limit = max_allocated; + if_debug7('0', "[0]space=%d, max_vm=%ld, prev.alloc=%ld, enabled=%d,\n\ + gc_alloc=%ld, threshold=%ld => limit=%ld\n", + mem->space, (long)mem->gc_status.max_vm, + (long)mem->previous_status.allocated, + mem->gc_status.enabled, (long)mem->gc_allocated, + (long)mem->gc_status.vm_threshold, (long)mem->limit); +} + +/* ================ Accessors ================ */ + +/* Get the size of an object from the header. */ +private uint +i_object_size(gs_memory_t *mem, const void /*obj_header_t*/ *obj) +{ return pre_obj_contents_size((const obj_header_t *)obj - 1); +} + +/* Get the type of a structure from the header. */ +private gs_memory_type_ptr_t +i_object_type(gs_memory_t *mem, const void /*obj_header_t*/ *obj) +{ return ((const obj_header_t *)obj - 1)->o_type; +} + +/* Get the GC status of a memory. */ +void +gs_memory_gc_status(const gs_ref_memory_t *mem, gs_memory_gc_status_t *pstat) +{ *pstat = mem->gc_status; +} + +/* Set the GC status of a memory. */ +void +gs_memory_set_gc_status(gs_ref_memory_t *mem, const gs_memory_gc_status_t *pstat) +{ mem->gc_status = *pstat; + ialloc_set_limit(mem); +} + +/* ================ Objects ================ */ + +/* Allocate a small object quickly if possible. */ +/* The size must be substantially less than max_uint. */ +/* ptr must be declared as obj_header_t *. */ +/* pfl must be declared as obj_header_t **. */ +#define IF_FREELIST_ALLOC(ptr, imem, size, pstype, pfl)\ + if ( size <= max_freelist_size &&\ + *(pfl = &imem->freelists[(size + obj_align_mask) >> log2_obj_align_mod]) != 0\ + )\ + { ptr = *pfl;\ + *pfl = *(obj_header_t **)ptr;\ + ptr[-1].o_size = size;\ + ptr[-1].o_type = pstype;\ + /* If debugging, clear the block in an attempt to */\ + /* track down uninitialized data errors. */\ + gs_alloc_fill(ptr, gs_alloc_fill_alloc, size); +#define ELSEIF_LIFO_ALLOC(ptr, imem, size, pstype)\ + }\ + else if ( (imem->cc.ctop - (byte *)(ptr = (obj_header_t *)imem->cc.cbot))\ + >= size + (obj_align_mod + sizeof(obj_header_t) * 2) &&\ + size < imem->large_size\ + )\ + { imem->cc.cbot = (byte *)ptr + obj_size_round(size);\ + ptr->o_large = 0;\ + ptr->o_size = size;\ + ptr->o_type = pstype;\ + ptr++;\ + /* If debugging, clear the block in an attempt to */\ + /* track down uninitialized data errors. */\ + gs_alloc_fill(ptr, gs_alloc_fill_alloc, size); +#define ELSE_ALLOC\ + }\ + else + +private byte * +i_alloc_bytes(gs_memory_t *mem, uint size, client_name_t cname) +{ obj_header_t *obj; + obj_header_t **pfl; + IF_FREELIST_ALLOC(obj, imem, size, &st_bytes, pfl) + if_debug4('A', "[a%d:+bF]%s -bytes-(%u) = 0x%lx\n", + imem->space, + client_name_string(cname), size, (ulong)obj); + ELSEIF_LIFO_ALLOC(obj, imem, size, &st_bytes) + if_debug4('A', "[a%d:+b ]%s -bytes-(%u) = 0x%lx\n", + imem->space, + client_name_string(cname), size, (ulong)obj); + ELSE_ALLOC + { obj = alloc_obj(imem, size, &st_bytes, false, cname); + if ( obj == 0 ) + return 0; + if_debug4('A', "[a%d:+b.]%s -bytes-(%u) = 0x%lx\n", + imem->space, + client_name_string(cname), size, (ulong)obj); + } + return (byte *)obj; +} +private byte * +i_alloc_bytes_immovable(gs_memory_t *mem, uint size, client_name_t cname) +{ obj_header_t *obj = alloc_obj(imem, size, &st_bytes, true, cname); + if ( obj == 0 ) + return 0; + if_debug4('A', "[a%d|+b.]%s -bytes-(%u) = 0x%lx\n", + imem->space, client_name_string(cname), size, (ulong)obj); + return (byte *)obj; +} +private void * +i_alloc_struct(gs_memory_t *mem, gs_memory_type_ptr_t pstype, + client_name_t cname) +{ uint size = pstype->ssize; + obj_header_t *obj; + obj_header_t **pfl; + IF_FREELIST_ALLOC(obj, imem, size, pstype, pfl) + if_debug5('A', "[a%d:+<F]%s %s(%u) = 0x%lx\n", + imem->space, client_name_string(cname), + struct_type_name_string(pstype), size, (ulong)obj); + ELSEIF_LIFO_ALLOC(obj, imem, size, pstype) + if_debug5('A', "[a%d:+< ]%s %s(%u) = 0x%lx\n", + imem->space, client_name_string(cname), + struct_type_name_string(pstype), size, (ulong)obj); + ELSE_ALLOC + { obj = alloc_obj(imem, size, pstype, false, cname); + if ( obj == 0 ) + return 0; + if_debug5('A', "[a%d:+<.]%s %s(%u) = 0x%lx\n", + imem->space, client_name_string(cname), + struct_type_name_string(pstype), size, (ulong)obj); + } + return obj; +} +private void * +i_alloc_struct_immovable(gs_memory_t *mem, gs_memory_type_ptr_t pstype, + client_name_t cname) +{ uint size = pstype->ssize; + obj_header_t *obj = alloc_obj(imem, size, pstype, true, cname); + if ( obj == 0 ) + return 0; + if_debug5('A', "[a%d|+<.]%s %s(%u) = 0x%lx\n", + imem->space, client_name_string(cname), + struct_type_name_string(pstype), size, (ulong)obj); + return obj; +} +private byte * +i_alloc_byte_array(gs_memory_t *mem, uint num_elements, uint elt_size, + client_name_t cname) +{ obj_header_t *obj = alloc_obj(imem, (ulong)num_elements * elt_size, + &st_bytes, false, cname); + if_debug6('A', "[a%d:+b.]%s -bytes-*(%lu=%u*%u) = 0x%lx\n", + imem->space, client_name_string(cname), + (ulong)num_elements * elt_size, + num_elements, elt_size, (ulong)obj); + return (byte *)obj; +} +private byte * +i_alloc_byte_array_immovable(gs_memory_t *mem, uint num_elements, + uint elt_size, client_name_t cname) +{ obj_header_t *obj = alloc_obj(imem, (ulong)num_elements * elt_size, + &st_bytes, true, cname); + if_debug6('A', "[a%d|+b.]%s -bytes-*(%lu=%u*%u) = 0x%lx\n", + imem->space, client_name_string(cname), + (ulong)num_elements * elt_size, + num_elements, elt_size, (ulong)obj); + return (byte *)obj; +} +private void * +i_alloc_struct_array(gs_memory_t *mem, uint num_elements, + gs_memory_type_ptr_t pstype, client_name_t cname) +{ obj_header_t *obj = alloc_obj(imem, + (ulong)num_elements * pstype->ssize, + pstype, false, cname); + if_debug7('A', "[a%d:+<.]%s %s*(%lu=%u*%u) = 0x%lx\n", + imem->space, + client_name_string(cname), struct_type_name_string(pstype), + (ulong)num_elements * pstype->ssize, + num_elements, pstype->ssize, (ulong)obj); + return (char *)obj; +} +private void * +i_alloc_struct_array_immovable(gs_memory_t *mem, uint num_elements, + gs_memory_type_ptr_t pstype, client_name_t cname) +{ obj_header_t *obj = alloc_obj(imem, + (ulong)num_elements * pstype->ssize, + pstype, true, cname); + if_debug7('A', "[a%d|+<.]%s %s*(%lu=%u*%u) = 0x%lx\n", + imem->space, + client_name_string(cname), struct_type_name_string(pstype), + (ulong)num_elements * pstype->ssize, + num_elements, pstype->ssize, (ulong)obj); + return (char *)obj; +} +private void * +i_resize_object(gs_memory_t *mem, void *obj, uint new_num_elements, + client_name_t cname) +{ obj_header_t *pp = (obj_header_t *)obj - 1; + gs_memory_type_ptr_t pstype = pp->o_type; + ulong old_size = pre_obj_contents_size(pp); + ulong new_size = (ulong)pstype->ssize * new_num_elements; + void *new_obj; + + if ( (byte *)obj + obj_align_round(old_size) == imem->cc.cbot && + imem->cc.ctop - imem->cc.cbot > new_size + obj_align_mod + ) + { imem->cc.cbot = (byte *)obj + obj_align_round(new_size); + pp->o_size = new_size; + if_debug8('A', "[a%d:%c%c ]%s %s(%lu=>%lu) 0x%lx\n", + imem->space, + (new_size > old_size ? '>' : '<'), + (pstype == &st_bytes ? 'b' : '<'), + client_name_string(cname), + struct_type_name_string(pstype), + old_size, new_size, (ulong)obj); + return obj; + } + /* Punt. */ + new_obj = gs_alloc_struct_array(mem, new_num_elements, void, + pstype, cname); + if ( new_obj == 0 ) + return 0; + memcpy(new_obj, obj, min(old_size, new_size)); + gs_free_object(mem, obj, cname); + return new_obj; +} +private void +i_free_object(gs_memory_t *mem, void *ptr, client_name_t cname) +{ obj_header_t *pp; + struct_proc_finalize((*finalize)); + uint size; + + if ( ptr == 0 ) + return; + pp = (obj_header_t *)ptr - 1; +#ifdef DEBUG + if ( gs_debug_c('?') ) + { chunk_locator_t cld; + + if ( pp->o_type == &st_free ) + { lprintf2("%s: object 0x%lx already free!\n", + client_name_string(cname), (ulong)ptr); + return;/*gs_abort();*/ + } + /* Check that this allocator owns the object being freed. */ + cld.memory = imem; + while ( (cld.cp = cld.memory->clast), + !chunk_locate_ptr(ptr, &cld) + ) + { if ( !cld.memory->saved ) + { lprintf3("%s: freeing 0x%lx, not owned by memory 0x%lx!\n", + client_name_string(cname), (ulong)ptr, + (ulong)mem); + return;/*gs_abort();*/ + } + /****** HACK: we know the saved state is the first ****** + ****** member of an alloc_save_t. ******/ + cld.memory = (gs_ref_memory_t *)cld.memory->saved; + } + /* Check that the object is in the allocated region. */ + if ( cld.memory == imem && cld.cp == imem->pcc ) + cld.cp = &imem->cc; + if ( !(ptr_between((const byte *)pp, cld.cp->cbase, + cld.cp->cbot)) + ) + { lprintf5("%s: freeing 0x%lx,\n\toutside chunk 0x%lx cbase=0x%lx, cbot=0x%lx!\n", + client_name_string(cname), (ulong)ptr, + (ulong)cld.cp, (ulong)cld.cp->cbase, + (ulong)cld.cp->cbot); + return;/*gs_abort();*/ + } + } +#endif + size = pre_obj_contents_size(pp); + finalize = pp->o_type->finalize; + if ( finalize != 0 ) + { if_debug3('u', "[u]finalizing %s 0x%lx (%s)\n", + struct_type_name_string(pp->o_type), + (ulong)ptr, client_name_string(cname)); + (*finalize)(ptr); + } + if ( (byte *)ptr + obj_align_round(size) == imem->cc.cbot ) + { if_debug4('A', "[a%d:-o ]%s(%u) 0x%lx\n", imem->space, + client_name_string(cname), size, (ulong)ptr); + gs_alloc_fill(ptr, gs_alloc_fill_free, size); + imem->cc.cbot = (byte *)pp; + return; + } + if ( pp->o_large ) + { /* + * We gave this object its own chunk. Free the entire chunk, + * unless it belongs to an older save level, in which case + * we mustn't overwrite it. + */ + chunk_locator_t cl; +#ifdef DEBUG + { chunk_locator_t cld; + cld.memory = imem; + cld.cp = 0; + if_debug5('a', "[a%d:-o%c]%s(%u) 0x%lx\n", imem->space, + (chunk_locate_ptr(ptr, &cld) ? 'L' : '~'), + client_name_string(cname), size, (ulong)ptr); + } +#endif + cl.memory = imem; + cl.cp = 0; + if ( chunk_locate_ptr(ptr, &cl) ) + { alloc_free_chunk(cl.cp, imem); + return; + } + /* Don't overwrite even if gs_alloc_debug is set. */ + } + if ( size <= max_freelist_size && + obj_align_round(size) >= sizeof(obj_header_t *) + ) + { /* + * Put the object on a freelist, unless it belongs to + * an older save level, in which case we mustn't + * overwrite it. + */ + imem->cfreed.memory = imem; + if ( chunk_locate(ptr, &imem->cfreed) ) + { obj_header_t **pfl = + &imem->freelists[(size + obj_align_mask) >> + log2_obj_align_mod]; + pp->o_type = &st_free; /* don't confuse GC */ + gs_alloc_fill(ptr, gs_alloc_fill_free, size); + *(obj_header_t **)ptr = *pfl; + *pfl = (obj_header_t *)ptr; + if_debug4('A', "[a%d:-oF]%s(%u) 0x%lx\n", + imem->space, client_name_string(cname), + size, (ulong)ptr); + return; + } + /* Don't overwrite even if gs_alloc_debug is set. */ + } + else + { pp->o_type = &st_free; /* don't confuse GC */ + gs_alloc_fill(ptr, gs_alloc_fill_free, size); + } + if_debug4('A', "[a%d:-o#]%s(%u) 0x%lx\n", imem->space, + client_name_string(cname), size, (ulong)ptr); + imem->lost.objects += obj_size_round(size); +} +private byte * +i_alloc_string(gs_memory_t *mem, uint nbytes, client_name_t cname) +{ byte *str; +top: if ( imem->cc.ctop - imem->cc.cbot > nbytes ) + { if_debug4('A', "[a%d:+> ]%s(%u) = 0x%lx\n", imem->space, + client_name_string(cname), nbytes, + (ulong)(imem->cc.ctop - nbytes)); + str = imem->cc.ctop -= nbytes; + gs_alloc_fill(str, gs_alloc_fill_alloc, nbytes); + return str; + } + if ( nbytes > string_space_quanta(max_uint - sizeof(chunk_head_t)) * + string_data_quantum + ) + { /* Can't represent the size in a uint! */ + return 0; + } + if ( nbytes >= imem->large_size ) + { /* Give it a chunk all its own. */ + return i_alloc_string_immovable(mem, nbytes, cname); + } + else + { /* Add another chunk. */ + chunk_t *cp = + alloc_add_chunk(imem, (ulong)imem->chunk_size, true, + "chunk"); + if ( cp == 0 ) + return 0; + alloc_close_chunk(imem); + imem->pcc = cp; + imem->cc = *imem->pcc; + gs_alloc_fill(imem->cc.cbase, gs_alloc_fill_free, + imem->cc.climit - imem->cc.cbase); + goto top; + } +} +private byte * +i_alloc_string_immovable(gs_memory_t *mem, uint nbytes, client_name_t cname) +{ byte *str; + /* Give it a chunk all its own. */ + uint asize = string_chunk_space(nbytes) + sizeof(chunk_head_t); + chunk_t *cp = alloc_add_chunk(imem, (ulong)asize, true, + "large string chunk"); + if ( cp == 0 ) + return 0; + str = cp->ctop = cp->climit - nbytes; + if_debug4('a', "[a%d|+>L]%s(%u) = 0x%lx\n", imem->space, + client_name_string(cname), nbytes, (ulong)str); + gs_alloc_fill(str, gs_alloc_fill_alloc, nbytes); + return str; +} +private byte * +i_resize_string(gs_memory_t *mem, byte *data, uint old_num, uint new_num, + client_name_t cname) +{ byte *ptr; + if ( data == imem->cc.ctop && + (new_num < old_num || + imem->cc.ctop - imem->cc.cbot > new_num - old_num) + ) + { /* Resize in place. */ + ptr = data + old_num - new_num; + if_debug6('A', "[a%d:%c> ]%s(%u->%u) 0x%lx\n", + imem->space, (new_num > old_num ? '>' : '<'), + client_name_string(cname), old_num, new_num, + (ulong)ptr); + imem->cc.ctop = ptr; + memmove(ptr, data, min(old_num, new_num)); +#ifdef DEBUG + if ( new_num > old_num ) + gs_alloc_fill(ptr + old_num, gs_alloc_fill_alloc, + new_num - old_num); + else + gs_alloc_fill(data, gs_alloc_fill_free, old_num - new_num); +#endif + } + else + { /* Punt. */ + ptr = gs_alloc_string(mem, new_num, cname); + if ( ptr == 0 ) + return 0; + memcpy(ptr, data, min(old_num, new_num)); + gs_free_string(mem, data, old_num, cname); + } + return ptr; +} +private void +i_free_string(gs_memory_t *mem, byte *data, uint nbytes, + client_name_t cname) +{ if ( data == imem->cc.ctop ) + { if_debug4('A', "[a%d:-> ]%s(%u) 0x%lx\n", imem->space, + client_name_string(cname), nbytes, (ulong)data); + imem->cc.ctop += nbytes; + } + else + { if_debug4('A', "[a%d:->#]%s(%u) 0x%lx\n", imem->space, + client_name_string(cname), nbytes, (ulong)data); + imem->lost.strings += nbytes; + } + gs_alloc_fill(data, gs_alloc_fill_free, nbytes); +} +private void +i_status(gs_memory_t *mem, gs_memory_status_t *pstat) +{ ulong unused = imem->lost.refs + imem->lost.strings; + ulong inner = 0; + + alloc_close_chunk(imem); + /* Add up unallocated space within each chunk. */ + /* Also keep track of space allocated to inner chunks, */ + /* which are included in previous_status.allocated. */ + { const chunk_t *cp = imem->cfirst; + while ( cp != 0 ) + { unused += cp->ctop - cp->cbot; + if ( cp->outer ) + inner += cp->cend - (byte *)cp->chead; + cp = cp->cnext; + } + } + unused += compute_free_objects(imem); + pstat->used = imem->allocated + inner - unused + + imem->previous_status.used; + pstat->allocated = imem->allocated + + imem->previous_status.allocated; +} + +private void +i_enable_free(gs_memory_t *mem, bool enable) +{ if ( enable ) + mem->procs.free_object = i_free_object, + mem->procs.free_string = i_free_string; + else + mem->procs.free_object = gs_ignore_free_object, + mem->procs.free_string = gs_ignore_free_string; +} + +/* ------ Internal procedures ------ */ + +/* Compute the amount of free object space by scanning free lists. */ +private ulong +compute_free_objects(gs_ref_memory_t *mem) +{ ulong unused = mem->lost.objects; + int i; + + /* Add up space on free lists. */ + for ( i = 0; i < num_freelists; i++ ) + { uint free_size = + (i << log2_obj_align_mod) + sizeof(obj_header_t); + const obj_header_t *pfree; + + for ( pfree = mem->freelists[i]; pfree != 0; + pfree = *(const obj_header_t * const *)pfree + ) + unused += free_size; + } + return unused; +} + +/* Allocate an object. This handles all but the fastest, simplest case. */ +private obj_header_t * +alloc_obj(gs_ref_memory_t *mem, ulong lsize, gs_memory_type_ptr_t pstype, + bool immovable, client_name_t cname) +{ obj_header_t *ptr; + if ( lsize >= mem->large_size || immovable ) + { ulong asize = + ((lsize + obj_align_mask) & -obj_align_mod) + + sizeof(obj_header_t); + /* Give it a chunk all its own. */ + chunk_t *cp = + alloc_add_chunk(mem, asize + sizeof(chunk_head_t), false, + "large object chunk"); + if ( cp == 0 ) + return 0; + ptr = (obj_header_t *)cp->cbot; + cp->cbot += asize; + ptr->o_large = 1; + pre_obj_set_large_size(ptr, lsize); + } + else + { uint asize = obj_size_round((uint)lsize); + while ( mem->cc.ctop - + (byte *)(ptr = (obj_header_t *)mem->cc.cbot) + <= asize + sizeof(obj_header_t) ) + { /* Add another chunk. */ + chunk_t *cp = + alloc_add_chunk(mem, (ulong)mem->chunk_size, + true, "chunk"); + if ( cp == 0 ) + return 0; + alloc_close_chunk(mem); + mem->pcc = cp; + mem->cc = *mem->pcc; + gs_alloc_fill(mem->cc.cbase, gs_alloc_fill_free, + mem->cc.climit - mem->cc.cbase); + } + mem->cc.cbot = (byte *)ptr + asize; + ptr->o_large = 0; + ptr->o_size = (uint)lsize; + } + ptr->o_type = pstype; + ptr++; + gs_alloc_fill(ptr, gs_alloc_fill_alloc, lsize); + return ptr; +} + +/* ================ Roots ================ */ + +/* Register a root. */ +private void +i_register_root(gs_memory_t *mem, gs_gc_root_t *rp, gs_ptr_type_t ptype, + void **up, client_name_t cname) +{ if_debug3('8', "[8]register root(%s) 0x%lx -> 0x%lx\n", + client_name_string(cname), (ulong)rp, (ulong)up); + rp->ptype = ptype, rp->p = up; + rp->next = imem->roots, imem->roots = rp; +} + +/* Unregister a root. */ +private void +i_unregister_root(gs_memory_t *mem, gs_gc_root_t *rp, client_name_t cname) +{ gs_gc_root_t **rpp = &imem->roots; + if_debug2('8', "[8]unregister root(%s) 0x%lx\n", + client_name_string(cname), (ulong)rp); + while ( *rpp != rp ) rpp = &(*rpp)->next; + *rpp = (*rpp)->next; +} + +/* ================ Chunks ================ */ + +public_st_chunk(); + +/* Insert a chunk in the chain. This is exported for the GC and for */ +/* the forget_save operation. */ +void +alloc_link_chunk(chunk_t *cp, gs_ref_memory_t *mem) +{ byte *cdata = cp->cbase; + chunk_t *icp; + chunk_t *prev; + for ( icp = mem->cfirst; icp != 0 && ptr_ge(cdata, icp->ctop); + icp = icp->cnext + ) + ; + cp->cnext = icp; + if ( icp == 0 ) /* add at end of chain */ + { prev = imem->clast; + imem->clast = cp; + } + else /* insert before icp */ + { prev = icp->cprev; + icp->cprev = cp; + } + cp->cprev = prev; + if ( prev == 0 ) + imem->cfirst = cp; + else + prev->cnext = cp; + if ( imem->pcc != 0 ) + { imem->cc.cnext = imem->pcc->cnext; + imem->cc.cprev = imem->pcc->cprev; + } +} + +/* Allocate a chunk. If we would exceed MaxLocalVM (if relevant), */ +/* or if we would exceed the VMThreshold and psignal is NULL, */ +/* return 0; if we would exceed the VMThreshold but psignal is valid, */ +/* just set the signal and return successfully. */ +private chunk_t * +alloc_add_chunk(gs_ref_memory_t *mem, ulong csize, bool has_strings, + client_name_t cname) +{ gs_memory_t *parent = mem->parent; + chunk_t *cp = gs_alloc_struct_immovable(parent, chunk_t, &st_chunk, + cname); + byte *cdata; + /* If csize is larger than max_uint, */ + /* we have to fake it using gs_alloc_byte_array. */ + ulong elt_size = csize; + uint num_elts = 1; + if ( (ulong)(mem->allocated + mem->inherited) >= mem->limit ) + { mem->gc_status.requested += csize; + if ( mem->limit >= mem->gc_status.max_vm || + mem->gc_status.psignal == 0 + ) + return 0; + if_debug4('0', "[0]signaling space=%d, allocated=%ld, limit=%ld, requested=%ld\n", + mem->space, (long)mem->allocated, + (long)mem->limit, (long)mem->gc_status.requested); + *mem->gc_status.psignal = mem->gc_status.signal_value; + } + while ( (uint)elt_size != elt_size ) + elt_size = (elt_size + 1) >> 1, + num_elts <<= 1; + cdata = gs_alloc_byte_array_immovable(parent, num_elts, elt_size, + cname); + if ( cp == 0 || cdata == 0 ) + { gs_free_object(parent, cdata, cname); + gs_free_object(parent, cp, cname); + mem->gc_status.requested = csize; + return 0; + } + alloc_init_chunk(cp, cdata, cdata + csize, has_strings, (chunk_t *)0); + alloc_link_chunk(cp, mem); + mem->allocated += + gs_object_size(parent, cdata) + gs_object_size(parent, cp); + return cp; +} + +/* Initialize the pointers in a chunk. This is exported for save/restore. */ +/* The bottom pointer must be aligned, but the top pointer need not */ +/* be aligned. */ +void +alloc_init_chunk(chunk_t *cp, byte *bot, byte *top, bool has_strings, + chunk_t *outer) +{ byte *cdata = bot; + if ( outer != 0 ) + outer->inner_count++; + cp->chead = (chunk_head_t *)cdata; + cdata += sizeof(chunk_head_t); + cp->cbot = cp->cbase = cdata; + cp->cend = top; + cp->rcur = 0; + cp->rtop = 0; + cp->outer = outer; + cp->inner_count = 0; + cp->has_refs = false; + cp->sbase = cdata; + if ( has_strings && top - cdata >= string_space_quantum + sizeof(long) - 1) + { /* + * We allocate a large enough string marking and reloc table + * to cover the entire chunk. + */ + uint nquanta = string_space_quanta(top - cdata); + cp->climit = cdata + nquanta * string_data_quantum; + cp->smark = cp->climit; + cp->smark_size = string_quanta_mark_size(nquanta); + cp->sreloc = + (string_reloc_offset *)(cp->smark + cp->smark_size); + cp->sfree1 = (ushort *)cp->sreloc; + } + else + { /* No strings, don't need the string GC tables. */ + cp->climit = cp->cend; + cp->sfree1 = 0; + cp->smark = 0; + cp->smark_size = 0; + cp->sreloc = 0; + } + cp->ctop = cp->climit; + alloc_init_free_strings(cp); +} + +/* Initialize the string freelists in a chunk. */ +void +alloc_init_free_strings(chunk_t *cp) +{ if ( cp->sfree1 ) + memset(cp->sfree1, 0, + ((cp->climit - csbase(cp) + 255) >> 8) * + sizeof(*cp->sfree1)); + cp->sfree = 0; +} + +/* Close up the current chunk. */ +/* This is exported for save/restore and the GC. */ +void +alloc_close_chunk(gs_ref_memory_t *mem) +{ if ( mem->pcc != 0 ) + { *mem->pcc = mem->cc; +#ifdef DEBUG + if ( gs_debug_c('a') ) + { dprintf1("[a%d]", mem->space); + dprintf_chunk("closing chunk", mem->pcc); + } +#endif + } +} + +/* Reopen the current chunk after a GC or restore. */ +void +alloc_open_chunk(gs_ref_memory_t *mem) +{ if ( mem->pcc != 0 ) + { mem->cc = *mem->pcc; +#ifdef DEBUG + if ( gs_debug_c('a') ) + { dprintf1("[a%d]", mem->space); + dprintf_chunk("opening chunk", mem->pcc); + } +#endif + } +} + +/* Remove a chunk from the chain. This is exported for the GC. */ +void +alloc_unlink_chunk(chunk_t *cp, gs_ref_memory_t *mem) +{ +#ifdef DEBUG + if ( gs_alloc_debug ) + { /* Check to make sure this chunk belongs to this allocator. */ + const chunk_t *ap = mem->cfirst; + while ( ap != 0 && ap != cp ) + ap = ap->cnext; + if ( ap != cp ) + { lprintf2("unlink_chunk 0x%lx not owned by memory 0x%lx!\n", + (ulong)cp, (ulong)mem); + return;/*gs_abort();*/ + } + } +#endif + if ( cp->cprev == 0 ) + mem->cfirst = cp->cnext; + else + cp->cprev->cnext = cp->cnext; + if ( cp->cnext == 0 ) + mem->clast = cp->cprev; + else + cp->cnext->cprev = cp->cprev; + if ( mem->pcc != 0 ) + { mem->cc.cnext = mem->pcc->cnext; + mem->cc.cprev = mem->pcc->cprev; + if ( mem->pcc == cp ) + { mem->pcc = 0; + mem->cc.cbot = mem->cc.ctop = 0; + } + } +} + +/* Free a chunk. This is exported for save/restore and for the GC. */ +void +alloc_free_chunk(chunk_t *cp, gs_ref_memory_t *mem) +{ gs_memory_t *parent = mem->parent; + alloc_unlink_chunk(cp, mem); + if ( mem->cfreed.cp == cp ) + mem->cfreed.cp = 0; + if ( cp->outer == 0 ) + { byte *cdata = (byte *)cp->chead; + mem->allocated -= gs_object_size(parent, cdata); + gs_free_object(parent, cdata, "alloc_free_chunk(data)"); + } + else + cp->outer->inner_count--; + mem->allocated -= gs_object_size(parent, cp); + gs_free_object(parent, cp, "alloc_free_chunk(chunk struct)"); +} + +/* Find the chunk for a pointer. */ +/* Note that this only searches the current save level. */ +/* Since a given save level can't contain both a chunk and an inner chunk */ +/* of that chunk, we can stop when is_within_chunk succeeds, and just test */ +/* is_in_inner_chunk then. */ +bool +chunk_locate_ptr(const void *vptr, chunk_locator_t *clp) +{ register chunk_t *cp = clp->cp; + if ( cp == 0 ) + { cp = clp->memory->cfirst; + if ( cp == 0 ) + return false; + } +#define ptr (const byte *)vptr + if ( ptr_lt(ptr, cp->cbase) ) + { do + { cp = cp->cprev; + if ( cp == 0 ) + return false; + } + while ( ptr_lt(ptr, cp->cbase) ); + if ( ptr_ge(ptr, cp->cend) ) + return false; + } + else + { while ( ptr_ge(ptr, cp->cend) ) + { cp = cp->cnext; + if ( cp == 0 ) + return false; + } + if ( ptr_lt(ptr, cp->cbase) ) + return false; + } + clp->cp = cp; + return !ptr_is_in_inner_chunk(ptr, cp); +#undef ptr +} + +/* ------ Debugging printout ------ */ + +/* + * All of this code should be in a separate file, but we added it just + * before a release, and it would have been too disruptive to add a new + * file at this point. + */ + +#ifdef DEBUG + +#include "string_.h" + +/* + * Define the options for a memory dump. These may be or'ed together. + */ +typedef enum { + dump_do_default = 0, /* pro forma */ + dump_do_strings = 1, + dump_do_type_addresses = 2, + dump_do_no_types = 4, + dump_do_pointers = 8, + dump_do_pointed_strings = 16, /* only if do_pointers also set */ + dump_do_contents = 32, + dump_do_marks = 64 +} dump_options_t; + +/* + * Define all the parameters controlling what gets dumped. + */ +typedef struct dump_control_s { + dump_options_t options; + const byte *bottom; + const byte *top; +} dump_control_t; +#define obj_in_control_region(obot, otop, pdc)\ + ( ((pdc)->bottom == NULL || (const byte *)(otop) > (pdc)->bottom) &&\ + ((pdc)->top == NULL || (const byte *)(obot) < (pdc)->top) ) +const dump_control_t dump_control_default = { + dump_do_default, NULL, NULL +}; +const dump_control_t dump_control_all = { + dump_do_strings | dump_do_type_addresses | dump_do_pointers | + dump_do_pointed_strings | dump_do_contents, NULL, NULL +}; + +/* + * Internal procedure to dump a block of memory, in hex and optionally + * also as characters. + */ +private void +debug_indent(int indent) +{ int i; + for ( i = indent; i > 0; --i ) + dputc(' '); +} +private void +debug_dump_contents(const byte *bot, const byte *top, int indent, + bool as_chars) +{ const byte *block; +#define block_size 16 + + if ( bot >= top ) + return; + for ( block = bot - ((bot - (byte *)0) & (block_size - 1)); + block < top; block += block_size + ) { + int i; + char label[12]; + + /* Check for repeated blocks. */ + if ( block >= bot + block_size && + block <= top - (block_size * 2) && + !memcmp(block, block - block_size, block_size) && + !memcmp(block, block + block_size, block_size) + ) { + if ( block < bot + block_size * 2 || + memcmp(block, block - block_size * 2, block_size) + ) { + debug_indent(indent); + dputs(" ...\n"); + } + continue; + } + sprintf(label, "0x%lx:", (ulong)block); + debug_indent(indent); + dputs(label); + for ( i = 0; i < block_size; ++i ) { + const char *sepr = ((i & 3) == 0 && i != 0 ? " " : " "); + + dputs(sepr); + if ( block + i >= bot && block + i < top ) + dprintf1("%02x", block[i]); + else + dputs(" "); + } + dputc('\n'); + if ( as_chars ) { + debug_indent(indent + strlen(label)); + for ( i = 0; i < block_size; ++i ) { + byte ch; + if ( (i & 3) == 0 && i != 0 ) + dputc(' '); + if ( block + i >= bot && block + i < top && + (ch = block[i]) >= 32 && ch <= 126 + ) + dprintf1(" %c", ch); + else + dputs(" "); + } + dputc('\n'); + } + } +#undef block_size +} + +/* Print one object with the given options. */ +/* Relevant options: type_addresses, no_types, pointers, pointed_strings, */ +/* contents. */ +void +debug_print_object(const void *obj, const dump_control_t *control) +{ const obj_header_t *pre = ((const obj_header_t *)obj) - 1; + ulong size = pre_obj_contents_size(pre); + const gs_memory_struct_type_t *type = pre->o_type; + dump_options_t options = control->options; + + dprintf3(" pre=0x%lx(obj=0x%lx) size=%lu", (ulong)pre, (ulong)obj, + size); + switch ( options & (dump_do_type_addresses | dump_do_no_types) ) + { + case dump_do_type_addresses + dump_do_no_types: /* addresses only */ + dprintf1(" type=0x%lx", (ulong)type); + break; + case dump_do_type_addresses: /* addresses & names */ + dprintf2(" type=%s(0x%lx)", struct_type_name_string(type), + (ulong)type); + break; + case 0: /* names only */ + dprintf1(" type=%s", struct_type_name_string(type)); + case dump_do_no_types: /* nothing */ + ; + } + if ( options & dump_do_marks ) { + if ( pre->o_large ) + dprintf1(" lmark=%d", pre->o_lmark); + else + dprintf2(" smark/back=%u (0x%x)", pre->o_smark, pre->o_smark); + } + dputc('\n'); + if ( type == &st_free ) + return; + if ( options & dump_do_pointers ) { + struct_proc_enum_ptrs((*proc)) = type->enum_ptrs; + uint index = 0; + const void *ptr; + gs_ptr_type_t ptype; + + /* + * NOTE: the following cast should be unnecessary, but that + * will require adding 'const' to the first prototype argument + * of struct_proc_enum_ptrs. + */ + if ( proc != 0 ) + for ( ; (ptype = (*proc)((obj_header_t *)pre + 1, size, index, &ptr)) != 0; + ++index + ) { + dprintf1(" ptr %u: ", index); + if ( ptype == ptr_string_type || ptype == ptr_const_string_type ) { + const gs_const_string *str = (const gs_const_string *)ptr; + + dprintf2("0x%lx(%u)", (ulong)str->data, str->size); + if ( options & dump_do_pointed_strings ) { + dputs(" =>\n"); + debug_dump_contents(str->data, str->data + str->size, 6, + true); + } else { + dputc('\n'); + } + } else { + dprintf1((ptr_between(ptr, obj, (const byte *)obj + size) ? + "(0x%lx)\n" : "0x%lx\n"), (ulong)ptr); + } + } + } + if ( options & dump_do_contents ) { + debug_dump_contents((const byte *)obj, (const byte *)obj + size, + 0, false); + } +} + +/* Print the contents of a chunk with the given options. */ +/* Relevant options: all. */ +void +debug_dump_chunk(const chunk_t *cp, const dump_control_t *control) +{ dprintf1("chunk at 0x%lx:\n", (ulong)cp); + dprintf3(" chead=0x%lx cbase=0x%lx sbase=0x%lx\n", + (ulong)cp->chead, (ulong)cp->cbase, (ulong)cp->sbase); + dprintf3(" rcur=0x%lx rtop=0x%lx cbot=0x%lx\n", + (ulong)cp->rcur, (ulong)cp->rtop, (ulong)cp->cbot); + dprintf4(" ctop=0x%lx climit=0x%lx smark=0x%lx, size=%u\n", + (ulong)cp->ctop, (ulong)cp->climit, (ulong)cp->smark, + cp->smark_size); + dprintf2(" sreloc=0x%lx cend=0x%lx\n", + (ulong)cp->sreloc, (ulong)cp->cend); + dprintf5("cprev=0x%lx cnext=0x%lx outer=0x%lx inner_count=%u has_refs=%s\n", + (ulong)cp->cprev, (ulong)cp->cnext, (ulong)cp->outer, + cp->inner_count, (cp->has_refs? "true" : "false")); + + dprintf2(" sfree1=0x%lx sfree=0x%x\n", + (ulong)cp->sfree1, cp->sfree); + if ( control->options & dump_do_strings ) { + debug_dump_contents((control->bottom == 0 ? cp->ctop : + max(control->bottom, cp->ctop)), + (control->top == 0 ? cp->climit : + min(control->top, cp->climit)), + 0, true); + } + SCAN_CHUNK_OBJECTS(cp) + DO_ALL + if ( obj_in_control_region(pre + 1, + (const byte *)(pre + 1) + size, + control) + ) + debug_print_object(pre + 1, control); +/* Temporarily redefine gs_exit so a chunk parsing error */ +/* won't actually exit. */ +#define gs_exit(n) DO_NOTHING + END_OBJECTS_SCAN +#undef gs_exit +} +void debug_print_chunk(const chunk_t *cp) +{ dump_control_t control; + + control = dump_control_default; + debug_dump_chunk(cp, &control); +} + +/* Print the contents of all chunks managed by an allocator. */ +/* Relevant options: all. */ +void +debug_dump_memory(const gs_ref_memory_t *mem, const dump_control_t *control) +{ const chunk_t *mcp; + + for ( mcp = mem->cfirst; mcp != 0; mcp = mcp->cnext ) { + const chunk_t *cp = (mcp == mem->pcc ? &mem->cc : mcp); + + if ( obj_in_control_region(cp->cbase, cp->cend, control) ) + debug_dump_chunk(cp, control); + } +} + +#endif /* DEBUG */ |