/* Copyright (C) 1993, 1995, 1996, 1998, 1999 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: ialloc.c,v 1.2.6.1.2.1 2003/01/17 00:49:04 giles Exp $ */
/* Memory allocator for Ghostscript interpreter */
#include "gx.h"
#include "memory_.h"
#include "errors.h"
#include "gsstruct.h"
#include "iref.h"		/* must precede iastate.h */
#include "iastate.h"
#include "igc.h"		/* for gs_gc_reclaim */
#include "ipacked.h"
#include "iutil.h"
#include "ivmspace.h"
#include "store.h"

/*
 * Define global and local instances.
 */
public_st_gs_dual_memory();

/* Initialize the allocator */
int
ialloc_init(gs_dual_memory_t *dmem, gs_raw_memory_t * rmem, uint chunk_size,
	    bool level2)
{
    gs_ref_memory_t *ilmem = ialloc_alloc_state(rmem, chunk_size);
    gs_ref_memory_t *ilmem_stable = ialloc_alloc_state(rmem, chunk_size);
    gs_ref_memory_t *igmem = 0;
    gs_ref_memory_t *igmem_stable = 0;
    gs_ref_memory_t *ismem = ialloc_alloc_state(rmem, chunk_size);
    int i;

    if (ilmem == 0 || ilmem_stable == 0 || ismem == 0)
	goto fail;
    ilmem->stable_memory = (gs_memory_t *)ilmem_stable;
    if (level2) {
	igmem = ialloc_alloc_state(rmem, chunk_size);
	igmem_stable = ialloc_alloc_state(rmem, chunk_size);
	if (igmem == 0 || igmem_stable == 0)
	    goto fail;
	igmem->stable_memory = (gs_memory_t *)igmem_stable;
    } else
	igmem = ilmem, igmem_stable = ilmem_stable;
    for (i = 0; i < countof(dmem->spaces_indexed); i++)
	dmem->spaces_indexed[i] = 0;
    dmem->space_local = ilmem;
    dmem->space_global = igmem;
    dmem->space_system = ismem;
    dmem->spaces.vm_reclaim = gs_gc_reclaim; /* real GC */
    dmem->reclaim = 0;		/* no interpreter GC yet */
    /* Level 1 systems have only local VM. */
    igmem->space = avm_global;
    igmem_stable->space = avm_global;
    ilmem->space = avm_local;	/* overrides if ilmem == igmem */
    ilmem_stable->space = avm_local; /* ditto */
    ismem->space = avm_system;
    ialloc_set_space(dmem, avm_global);
    return 0;
 fail:
    gs_free_object(rmem, igmem_stable, "ialloc_init failure");
    gs_free_object(rmem, igmem, "ialloc_init failure");
    gs_free_object(rmem, ismem, "ialloc_init failure");
    gs_free_object(rmem, ilmem_stable, "ialloc_init failure");
    gs_free_object(rmem, ilmem, "ialloc_init failure");
    return_error(e_VMerror);
}

/* ================ Local/global VM ================ */

/* Get the space attribute of an allocator */
uint
imemory_space(const gs_ref_memory_t * iimem)
{
    return iimem->space;
}

/* Select the allocation space. */
void
ialloc_set_space(gs_dual_memory_t * dmem, uint space)
{
    gs_ref_memory_t *mem = dmem->spaces_indexed[space >> r_space_shift];

    dmem->current = mem;
    dmem->current_space = mem->space;
}

/* Get the l_new attribute of a current allocator. */
/* (A copy of the new_mask in the gs_dual_memory_t.) */
uint
imemory_new_mask(const gs_ref_memory_t *imem)
{
    return imem->new_mask;
}

/* Get the save level of an allocator. */
int
imemory_save_level(const gs_ref_memory_t *imem)
{
    return imem->save_level;
}

/* Reset the requests. */
void
ialloc_reset_requested(gs_dual_memory_t * dmem)
{
    dmem->space_system->gc_status.requested = 0;
    dmem->space_global->gc_status.requested = 0;
    dmem->space_local->gc_status.requested = 0;
}

/* ================ Refs ================ */

#ifdef DEBUG
private int
ialloc_trace_space(const gs_ref_memory_t *imem)
{
    return imem->space + (imem->stable_memory == (gs_memory_t *)imem);
}
#endif

/* Register a ref root. */
int
gs_register_ref_root(gs_memory_t *mem, gs_gc_root_t *root,
		     void **pp, client_name_t cname)
{
    return gs_register_root(mem, root, ptr_ref_type, pp, cname);
}

/*
 * As noted in iastate.h, every run of refs has an extra ref at the end
 * to hold relocation information for the garbage collector;
 * since sizeof(ref) % obj_align_mod == 0, we never need to
 * allocate any additional padding space at the end of the block.
 */

/* Allocate an array of refs. */
int
gs_alloc_ref_array(gs_ref_memory_t * mem, ref * parr, uint attrs,
		   uint num_refs, client_name_t cname)
{
    ref *obj;

    /* If we're allocating a run of refs already, */
    /* and we aren't about to overflow the maximum run length, use it. */
    if (mem->cc.rtop == mem->cc.cbot &&
	num_refs < (mem->cc.ctop - mem->cc.cbot) / sizeof(ref) &&
	mem->cc.rtop - (byte *) mem->cc.rcur + num_refs * sizeof(ref) <
	max_size_st_refs
	) {
	ref *end;

	obj = (ref *) mem->cc.rtop - 1;		/* back up over last ref */
	if_debug4('A', "[a%d:+$ ]%s(%u) = 0x%lx\n",
		  ialloc_trace_space(mem), client_name_string(cname),
		  num_refs, (ulong) obj);
	mem->cc.rcur[-1].o_size += num_refs * sizeof(ref);
	end = (ref *) (mem->cc.rtop = mem->cc.cbot +=
		       num_refs * sizeof(ref));
	make_mark(end - 1);
    } else {
	/*
	 * Allocate a new run.  We have to distinguish 3 cases:
	 *      - Same chunk: pcc unchanged, end == cc.cbot.
	 *      - Large chunk: pcc unchanged, end != cc.cbot.
	 *      - New chunk: pcc changed.
	 */
	chunk_t *pcc = mem->pcc;
	ref *end;

	obj = gs_alloc_struct_array((gs_memory_t *) mem, num_refs + 1,
				    ref, &st_refs, cname);
	if (obj == 0)
	    return_error(e_VMerror);
	/* Set the terminating ref now. */
	end = (ref *) obj + num_refs;
	make_mark(end);
	/* Set has_refs in the chunk. */
	if (mem->pcc != pcc || mem->cc.cbot == (byte *) (end + 1)) {
	    /* Ordinary chunk. */
	    mem->cc.rcur = (obj_header_t *) obj;
	    mem->cc.rtop = (byte *) (end + 1);
	    mem->cc.has_refs = true;
	} else {
	    /* Large chunk. */
	    /* This happens only for very large arrays, */
	    /* so it doesn't need to be cheap. */
	    chunk_locator_t cl;

	    cl.memory = mem;
	    cl.cp = mem->clast;
	    chunk_locate_ptr(obj, &cl);
	    cl.cp->has_refs = true;
	}
    }
    make_array(parr, attrs | mem->space, num_refs, obj);
    return 0;
}

/* Resize an array of refs.  Currently this is only implemented */
/* for shrinking, not for growing. */
int
gs_resize_ref_array(gs_ref_memory_t * mem, ref * parr,
		    uint new_num_refs, client_name_t cname)
{
    uint old_num_refs = r_size(parr);
    uint diff;
    ref *obj = parr->value.refs;

    if (new_num_refs > old_num_refs || !r_has_type(parr, t_array))
	return_error(e_Fatal);
    diff = old_num_refs - new_num_refs;
    /* Check for LIFO.  See gs_free_ref_array for more details. */
    if (mem->cc.rtop == mem->cc.cbot &&
	(byte *) (obj + (old_num_refs + 1)) == mem->cc.rtop
	) {
	/* Shorten the refs object. */
	ref *end = (ref *) (mem->cc.cbot = mem->cc.rtop -=
			    diff * sizeof(ref));

	if_debug4('A', "[a%d:<$ ]%s(%u) 0x%lx\n",
		  ialloc_trace_space(mem), client_name_string(cname), diff,
		  (ulong) obj);
	mem->cc.rcur[-1].o_size -= diff * sizeof(ref);
	make_mark(end - 1);
    } else {
	/* Punt. */
	if_debug4('A', "[a%d:<$#]%s(%u) 0x%lx\n",
		  ialloc_trace_space(mem), client_name_string(cname), diff,
		  (ulong) obj);
	mem->lost.refs += diff * sizeof(ref);
    }
    r_set_size(parr, new_num_refs);
    return 0;
}

/* Deallocate an array of refs.  Only do this if LIFO, or if */
/* the array occupies an entire chunk by itself. */
void
gs_free_ref_array(gs_ref_memory_t * mem, ref * parr, client_name_t cname)
{
    uint num_refs = r_size(parr);
    ref *obj = parr->value.refs;

    /*
     * Compute the storage size of the array, and check for LIFO
     * freeing or a separate chunk.  Note that the array might be packed;
     * for the moment, if it's anything but a t_array, punt.
     * The +1s are for the extra ref for the GC.
     */
    if (!r_has_type(parr, t_array))
	DO_NOTHING;		/* don't look for special cases */
    else if (mem->cc.rtop == mem->cc.cbot &&
	     (byte *) (obj + (num_refs + 1)) == mem->cc.rtop
	) {
	if ((obj_header_t *) obj == mem->cc.rcur) {
	    /* Deallocate the entire refs object. */
	    gs_free_object((gs_memory_t *) mem, obj, cname);
	    mem->cc.rcur = 0;
	    mem->cc.rtop = 0;
	} else {
	    /* Deallocate it at the end of the refs object. */
	    if_debug4('A', "[a%d:-$ ]%s(%u) 0x%lx\n",
		      ialloc_trace_space(mem), client_name_string(cname),
		      num_refs, (ulong) obj);
	    mem->cc.rcur[-1].o_size -= num_refs * sizeof(ref);
	    mem->cc.rtop = mem->cc.cbot = (byte *) (obj + 1);
	    make_mark(obj);
	}
	return;
    } else if (num_refs >= (mem->large_size / arch_sizeof_ref - 1)) {
	/* See if this array has a chunk all to itself. */
	/* We only make this check when freeing very large objects, */
	/* so it doesn't need to be cheap. */
	chunk_locator_t cl;

	cl.memory = mem;
	cl.cp = mem->clast;
	if (chunk_locate_ptr(obj, &cl) &&
	    obj == (ref *) ((obj_header_t *) (cl.cp->cbase) + 1) &&
	    (byte *) (obj + (num_refs + 1)) == cl.cp->cend
	    ) {
	    /* Free the chunk. */
	    if_debug4('a', "[a%d:-$L]%s(%u) 0x%lx\n",
		      ialloc_trace_space(mem), client_name_string(cname),
		      num_refs, (ulong) obj);
	    alloc_free_chunk(cl.cp, mem);
	    return;
	}
    }
    /* Punt, but fill the array with nulls so that there won't be */
    /* dangling references to confuse the garbage collector. */
    if_debug4('A', "[a%d:-$#]%s(%u) 0x%lx\n",
	      ialloc_trace_space(mem), client_name_string(cname), num_refs,
	      (ulong) obj);
    {
	uint size;

	switch (r_type(parr)) {
	    case t_shortarray:
		size = num_refs * sizeof(ref_packed);
		break;
	    case t_mixedarray:{
		/* We have to parse the array to compute the storage size. */
		uint i = 0;
		const ref_packed *p = parr->value.packed;

		for (; i < num_refs; ++i)
		    p = packed_next(p);
		size = (const byte *)p - (const byte *)parr->value.packed;
		break;
	    }
	    case t_array:
		size = num_refs * sizeof(ref);
		break;
	    default:
		lprintf3("Unknown type 0x%x in free_ref_array(%u,0x%lx)!",
			 r_type(parr), num_refs, (ulong) obj);
		return;
	}
	/*
	 * If there are any leftover packed elements, we don't
	 * worry about them, since they can't be dangling references.
	 */
	refset_null_new(obj, size / sizeof(ref), 0);
	mem->lost.refs += size;
    }
}

/* Allocate a string ref. */
int
gs_alloc_string_ref(gs_ref_memory_t * mem, ref * psref,
		    uint attrs, uint nbytes, client_name_t cname)
{
    byte *str = gs_alloc_string((gs_memory_t *) mem, nbytes, cname);

    if (str == 0)
	return_error(e_VMerror);
    make_string(psref, attrs | mem->space, nbytes, str);
    return 0;
}
