/* $Id$ $Revision$ */
/* vim:set shiftwidth=4 ts=8: */

/*************************************************************************
 * Copyright (c) 2011 AT&T Intellectual Property 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: See CVS logs. Details at http://www.graphviz.org/
 *************************************************************************/

#include	"vmhdr.h"

#define POOLFREE	0x55555555L	/* block free indicator  */

/*	Method for pool allocation.
**	All elements in a pool have the same size.
**	The following fields of Vmdata_t are used as:
**		pool:	size of a block.
**		free:	list of free blocks.
**
**	Written by Kiem-Phong Vo, kpv@research.att.com, 01/16/94.
*/

static void *poolalloc(Vmalloc_t * vm, reg size_t size)
{
    reg Vmdata_t *vd = vm->data;
    reg Block_t *tp, *next;
    reg size_t s;
    reg Seg_t *seg;
    reg int local;

    if (size <= 0)
	return NIL(void *);
    else if (size != vd->pool) {
	if (vd->pool <= 0)
	    vd->pool = size;
	else
	    return NIL(void *);
    }

    if (!(local = vd->mode & VM_TRUST)) {
	if (ISLOCK(vd, 0))
	    return NIL(void *);
	SETLOCK(vd, 0);
    }

    if ((tp = vd->free)) {	/* there is a ready free block */
	vd->free = SEGLINK(tp);
	goto done;
    }

    size = ROUND(size, ALIGN);

    /* look thru all segments for a suitable free block */
    for (tp = NIL(Block_t *), seg = vd->seg; seg; seg = seg->next) {
	if ((tp = seg->free) &&
	    (s = (SIZE(tp) & ~BITS) + sizeof(Head_t)) >= size)
	    goto has_blk;
    }

    for (;;) {			/* must extend region */
	if ((tp =
	     (*_Vmextend) (vm, ROUND(size, vd->incr), NIL(Vmsearch_f)))) {
	    s = (SIZE(tp) & ~BITS) + sizeof(Head_t);
	    seg = SEG(tp);
	    goto has_blk;
	} else if (vd->mode & VM_AGAIN)
	    vd->mode &= ~VM_AGAIN;
	else
	    goto done;
    }

  has_blk:			/* if get here, (tp, s, seg) must be well-defined */
    next = (Block_t *) ((Vmuchar_t *) tp + size);
    if ((s -= size) <= (size + sizeof(Head_t))) {
	for (; s >= size; s -= size) {
	    SIZE(next) = POOLFREE;
	    SEGLINK(next) = vd->free;
	    vd->free = next;
	    next = (Block_t *) ((Vmuchar_t *) next + size);
	}
	seg->free = NIL(Block_t *);
    } else {
	SIZE(next) = s - sizeof(Head_t);
	SEG(next) = seg;
	seg->free = next;
    }

  done:
    if (!local && (vd->mode & VM_TRACE) && _Vmtrace && tp)
	(*_Vmtrace) (vm, NIL(Vmuchar_t *), (Vmuchar_t *) tp, vd->pool, 0);

    CLRLOCK(vd, 0);
    return (void *) tp;
}

static long pooladdr(Vmalloc_t * vm, reg void * addr)
{
    reg Block_t *bp, *tp;
    reg Vmuchar_t *laddr, *baddr;
    reg size_t size;
    reg Seg_t *seg;
    reg long offset;
    reg Vmdata_t *vd = vm->data;
    reg int local;

    if (!(local = vd->mode & VM_TRUST)) {
	GETLOCAL(vd, local);
	if (ISLOCK(vd, local))
	    return -1L;
	SETLOCK(vd, local);
    }

    offset = -1L;
    for (seg = vd->seg; seg; seg = seg->next) {
	laddr = (Vmuchar_t *) SEGBLOCK(seg);
	baddr = seg->baddr - sizeof(Head_t);
	if ((Vmuchar_t *) addr < laddr || (Vmuchar_t *) addr >= baddr)
	    continue;

	/* the block that has this address */
	size = ROUND(vd->pool, ALIGN);
	tp = (Block_t *) (laddr +
			  (((Vmuchar_t *) addr - laddr) / size) * size);

	/* see if this block has been freed */
	if (SIZE(tp) == POOLFREE)	/* may be a coincidence - make sure */
	    for (bp = vd->free; bp; bp = SEGLINK(bp))
		if (bp == tp)
		    goto done;

	offset = (Vmuchar_t *) addr - (Vmuchar_t *) tp;
	goto done;
    }

  done:
    CLRLOCK(vd, local);
    return offset;
}

static int poolfree(reg Vmalloc_t * vm, reg void * data)
{
    reg Block_t *bp;
    reg Vmdata_t *vd = vm->data;
    reg int local;

    if (!data)
	return 0;

    if (!(local = vd->mode & VM_TRUST)) {
	if (ISLOCK(vd, 0) || vd->pool <= 0)
	    return -1;

	if (KPVADDR(vm, data, pooladdr) != 0) {
	    if (vm->disc->exceptf)
		(void) (*vm->disc->exceptf) (vm, VM_BADADDR, data,
					     vm->disc);
	    return -1;
	}

	SETLOCK(vd, 0);
    }

    bp = (Block_t *) data;
    SIZE(bp) = POOLFREE;
    SEGLINK(bp) = vd->free;
    vd->free = bp;

    if (!local && (vd->mode & VM_TRACE) && _Vmtrace)
	(*_Vmtrace) (vm, (Vmuchar_t *) data, NIL(Vmuchar_t *), vd->pool,
		     0);

    CLRLOCK(vd, local);
    return 0;
}

static void *poolresize(Vmalloc_t * vm, void * data, size_t size,
			  int type)
{
    reg Vmdata_t *vd = vm->data;

    NOTUSED(type);

    if (!data) {
	if ((data = poolalloc(vm, size)) && (type & VM_RSZERO)) {
	    reg int *d = (int *) data, *ed =
		(int *) ((char *) data + size);
	    do {
		*d++ = 0;
	    } while (d < ed);
	}
	return data;
    }
    if (size == 0) {
	(void) poolfree(vm, data);
	return NIL(void *);
    }

    if (!(vd->mode & VM_TRUST)) {
	if (ISLOCK(vd, 0))
	    return NIL(void *);

	if (size != vd->pool || KPVADDR(vm, data, pooladdr) != 0) {
	    if (vm->disc->exceptf)
		(void) (*vm->disc->exceptf) (vm, VM_BADADDR, data,
					     vm->disc);
	    return NIL(void *);
	}

	if ((vd->mode & VM_TRACE) && _Vmtrace)
	    (*_Vmtrace) (vm, (Vmuchar_t *) data, (Vmuchar_t *) data, size,
			 0);
    }

    return data;
}

static long poolsize(Vmalloc_t * vm, void * addr)
{
    return pooladdr(vm, addr) == 0 ? (long) vm->data->pool : -1L;
}

static int poolcompact(Vmalloc_t * vm)
{
    reg Block_t *fp;
    reg Seg_t *seg, *next;
    reg size_t s;
    reg Vmdata_t *vd = vm->data;

    if (!(vd->mode & VM_TRUST)) {
	if (ISLOCK(vd, 0))
	    return -1;
	SETLOCK(vd, 0);
    }

    for (seg = vd->seg; seg; seg = next) {
	next = seg->next;

	if (!(fp = seg->free))
	    continue;

	seg->free = NIL(Block_t *);
	if (seg->size == (s = SIZE(fp) & ~BITS))
	    s = seg->extent;
	else
	    s += sizeof(Head_t);

	if ((*_Vmtruncate) (vm, seg, s, 1) < 0)
	    seg->free = fp;
    }

    if ((vd->mode & VM_TRACE) && _Vmtrace)
	(*_Vmtrace) (vm, (Vmuchar_t *) 0, (Vmuchar_t *) 0, 0, 0);

    CLRLOCK(vd, 0);
    return 0;
}

static void *poolalign(Vmalloc_t * vm, size_t size, size_t align)
{
    NOTUSED(vm);
    NOTUSED(size);
    NOTUSED(align);
    return NIL(void *);
}

/* Public interface */
static Vmethod_t _Vmpool = {
    poolalloc,
    poolresize,
    poolfree,
    pooladdr,
    poolsize,
    poolcompact,
    poolalign,
    VM_MTPOOL
};

Vmethod_t* Vmpool = &_Vmpool;
